From f2481513315cd74691d9d5e5b5bfa895366bb69d Mon Sep 17 00:00:00 2001 From: Fisual Date: Wed, 15 Jun 2022 19:04:06 +0700 Subject: [PATCH] added tiers, vstdlib --- public/tier2/keyvaluesmacros.h | 22 + tier0/DESKey/ALGO.H | 68 + tier0/DESKey/ALGO32.LIB | Bin 0 -> 8878 bytes tier0/DESKey/DK2WIN32.H | 366 ++++ tier0/DESKey/DK2WIN32.LIB | Bin 0 -> 24300 bytes tier0/InterlockedCompareExchange128.masm | 78 + tier0/PMELib.cpp | 665 ++++++ tier0/ValveETWProviderEvents.h | 1700 +++++++++++++++ tier0/ValveETWProviderEvents.rc | 3 + tier0/ValveETWProviderEventsTEMP.BIN | Bin 0 -> 12762 bytes tier0/ValveETWProviderEvents_MSG00001.bin | Bin 0 -> 48 bytes tier0/assert_dialog.cpp | 616 ++++++ tier0/assert_dialog.rc | 124 ++ tier0/commandline.cpp | 696 ++++++ tier0/cpu.cpp | 596 +++++ tier0/cpu_posix.cpp | 177 ++ tier0/cpu_usage.cpp | 132 ++ tier0/cpumonitoring.cpp | 398 ++++ tier0/dbg.cpp | 948 ++++++++ tier0/dynfunction.cpp | 148 ++ tier0/etwprof.cpp | 406 ++++ tier0/extendedtrace.cpp | 407 ++++ tier0/extendedtrace.h | 63 + tier0/fasttimer.cpp | 51 + tier0/mem.cpp | 96 + tier0/mem_helpers.cpp | 172 ++ tier0/mem_helpers.h | 38 + tier0/memdbg.cpp | 1955 +++++++++++++++++ tier0/meminit.cpp | 9 + tier0/memstd.cpp | 1900 ++++++++++++++++ tier0/memstd.h | 292 +++ tier0/memvalidate.cpp | 485 +++++ tier0/minidump.cpp | 687 ++++++ tier0/pch_tier0.cpp | 9 + tier0/pch_tier0.h | 57 + tier0/platform.cpp | 291 +++ tier0/platform_posix.cpp | 1083 ++++++++++ tier0/pmc360.cpp | 88 + tier0/pme.cpp | 152 ++ tier0/pme_posix.cpp | 51 + tier0/progressbar.cpp | 37 + tier0/resource.h | 35 + tier0/security.cpp | 111 + tier0/security_linux.cpp | 125 ++ tier0/stacktools.cpp | 1707 +++++++++++++++ tier0/systeminformation.cpp | 310 +++ tier0/thread.cpp | 219 ++ tier0/threadtools.cpp | 2404 +++++++++++++++++++++ tier0/tier0.vpc | 267 +++ tier0/tier0_exclude.vpc | 5 +- tier0/tier0_staticlink.vpc | 3 + tier0/tier0_strtools.cpp | 53 + tier0/tier0_strtools.h | 3 + tier0/tslist.cpp | 538 +++++ tier0/validator.cpp | 268 +++ tier0/valobject.cpp | 120 + tier0/valveetwprovider.man | 288 +++ tier0/vcrmode.cpp | 1778 +++++++++++++++ tier0/vcrmode_posix.cpp | 970 +++++++++ tier0/vcrmode_xbox.cpp | 290 +++ tier0/vprof.cpp | 2094 ++++++++++++++++++ tier0/win32consoleio.cpp | 91 + tier0/xbox/xbox_console.cpp | 31 + tier0/xbox/xbox_profile.cpp | 9 + tier0/xbox/xbox_system.cpp | 368 ++++ tier0/xbox/xbox_win32stubs.cpp | 617 ++++++ tier1/strtools.cpp | 38 + tier2/beamsegdraw.cpp | 235 ++ tier2/camerautils.cpp | 99 + tier2/defaultfilesystem.cpp | 57 + tier2/dmconnect.cpp | 62 + tier2/fileutils.cpp | 304 +++ tier2/keybindings.cpp | 143 ++ tier2/keyvaluesmacros.cpp | 462 ++++ tier2/meshutils.cpp | 104 + tier2/p4helpers.cpp | 138 ++ tier2/renderutils.cpp | 916 ++++++++ tier2/riff.cpp | 506 +++++ tier2/soundutils.cpp | 237 ++ tier2/tier2.cpp | 117 + tier2/tier2.vpc | 58 + tier2/util_init.cpp | 50 + tier2/utlstreambuffer.cpp | 386 ++++ tier2/vconfig.cpp | 149 ++ tier3/choreoutils.cpp | 347 +++ tier3/mdlutils.cpp | 459 ++++ tier3/scenetokenprocessor.cpp | 201 ++ tier3/studiohdrstub.cpp | 47 + tier3/tier3.cpp | 154 ++ tier3/tier3.vpc | 29 + vpc_scripts/projects.vgc | 88 +- vstdlib/KeyValuesSystem.cpp | 416 ++++ vstdlib/coroutine.cpp | 1157 ++++++++++ vstdlib/coroutine_osx.vpc | 31 + vstdlib/coroutine_win64.masm | 175 ++ vstdlib/cvar.cpp | 899 ++++++++ vstdlib/getstackptr64.masm | 17 + vstdlib/jobthread.cpp | 1457 +++++++++++++ vstdlib/osversion.cpp | 447 ++++ vstdlib/processutils.cpp | 473 ++++ vstdlib/random.cpp | 265 +++ vstdlib/vcover.cpp | 9 + vstdlib/vstdlib.vpc | 157 ++ vstdlib/xbox/___FirstModule.cpp | 19 + 104 files changed, 38642 insertions(+), 36 deletions(-) create mode 100644 public/tier2/keyvaluesmacros.h create mode 100644 tier0/DESKey/ALGO.H create mode 100644 tier0/DESKey/ALGO32.LIB create mode 100644 tier0/DESKey/DK2WIN32.H create mode 100644 tier0/DESKey/DK2WIN32.LIB create mode 100644 tier0/InterlockedCompareExchange128.masm create mode 100644 tier0/PMELib.cpp create mode 100644 tier0/ValveETWProviderEvents.h create mode 100644 tier0/ValveETWProviderEvents.rc create mode 100644 tier0/ValveETWProviderEventsTEMP.BIN create mode 100644 tier0/ValveETWProviderEvents_MSG00001.bin create mode 100644 tier0/assert_dialog.cpp create mode 100644 tier0/assert_dialog.rc create mode 100644 tier0/commandline.cpp create mode 100644 tier0/cpu.cpp create mode 100644 tier0/cpu_posix.cpp create mode 100644 tier0/cpu_usage.cpp create mode 100644 tier0/cpumonitoring.cpp create mode 100644 tier0/dbg.cpp create mode 100644 tier0/dynfunction.cpp create mode 100644 tier0/etwprof.cpp create mode 100644 tier0/extendedtrace.cpp create mode 100644 tier0/extendedtrace.h create mode 100644 tier0/fasttimer.cpp create mode 100644 tier0/mem.cpp create mode 100644 tier0/mem_helpers.cpp create mode 100644 tier0/mem_helpers.h create mode 100644 tier0/memdbg.cpp create mode 100644 tier0/meminit.cpp create mode 100644 tier0/memstd.cpp create mode 100644 tier0/memstd.h create mode 100644 tier0/memvalidate.cpp create mode 100644 tier0/minidump.cpp create mode 100644 tier0/pch_tier0.cpp create mode 100644 tier0/pch_tier0.h create mode 100644 tier0/platform.cpp create mode 100644 tier0/platform_posix.cpp create mode 100644 tier0/pmc360.cpp create mode 100644 tier0/pme.cpp create mode 100644 tier0/pme_posix.cpp create mode 100644 tier0/progressbar.cpp create mode 100644 tier0/resource.h create mode 100644 tier0/security.cpp create mode 100644 tier0/security_linux.cpp create mode 100644 tier0/stacktools.cpp create mode 100644 tier0/systeminformation.cpp create mode 100644 tier0/thread.cpp create mode 100644 tier0/threadtools.cpp create mode 100644 tier0/tier0.vpc create mode 100644 tier0/tier0_staticlink.vpc create mode 100644 tier0/tier0_strtools.cpp create mode 100644 tier0/tier0_strtools.h create mode 100644 tier0/tslist.cpp create mode 100644 tier0/validator.cpp create mode 100644 tier0/valobject.cpp create mode 100644 tier0/valveetwprovider.man create mode 100644 tier0/vcrmode.cpp create mode 100644 tier0/vcrmode_posix.cpp create mode 100644 tier0/vcrmode_xbox.cpp create mode 100644 tier0/vprof.cpp create mode 100644 tier0/win32consoleio.cpp create mode 100644 tier0/xbox/xbox_console.cpp create mode 100644 tier0/xbox/xbox_profile.cpp create mode 100644 tier0/xbox/xbox_system.cpp create mode 100644 tier0/xbox/xbox_win32stubs.cpp create mode 100644 tier2/beamsegdraw.cpp create mode 100644 tier2/camerautils.cpp create mode 100644 tier2/defaultfilesystem.cpp create mode 100644 tier2/dmconnect.cpp create mode 100644 tier2/fileutils.cpp create mode 100644 tier2/keybindings.cpp create mode 100644 tier2/keyvaluesmacros.cpp create mode 100644 tier2/meshutils.cpp create mode 100644 tier2/p4helpers.cpp create mode 100644 tier2/renderutils.cpp create mode 100644 tier2/riff.cpp create mode 100644 tier2/soundutils.cpp create mode 100644 tier2/tier2.cpp create mode 100644 tier2/tier2.vpc create mode 100644 tier2/util_init.cpp create mode 100644 tier2/utlstreambuffer.cpp create mode 100644 tier2/vconfig.cpp create mode 100644 tier3/choreoutils.cpp create mode 100644 tier3/mdlutils.cpp create mode 100644 tier3/scenetokenprocessor.cpp create mode 100644 tier3/studiohdrstub.cpp create mode 100644 tier3/tier3.cpp create mode 100644 tier3/tier3.vpc create mode 100644 vstdlib/KeyValuesSystem.cpp create mode 100644 vstdlib/coroutine.cpp create mode 100644 vstdlib/coroutine_osx.vpc create mode 100644 vstdlib/coroutine_win64.masm create mode 100644 vstdlib/cvar.cpp create mode 100644 vstdlib/getstackptr64.masm create mode 100644 vstdlib/jobthread.cpp create mode 100644 vstdlib/osversion.cpp create mode 100644 vstdlib/processutils.cpp create mode 100644 vstdlib/random.cpp create mode 100644 vstdlib/vcover.cpp create mode 100644 vstdlib/vstdlib.vpc create mode 100644 vstdlib/xbox/___FirstModule.cpp diff --git a/public/tier2/keyvaluesmacros.h b/public/tier2/keyvaluesmacros.h new file mode 100644 index 0000000..93d0c26 --- /dev/null +++ b/public/tier2/keyvaluesmacros.h @@ -0,0 +1,22 @@ +//===================== Copyright (c) Valve Corporation. All Rights Reserved. ====================== +// +//================================================================================================== + +#pragma once + + +//-------------------------------------------------------------------------------------------------- +// Returns true if the passed string matches the filename style glob, false otherwise +// * matches any characters, ? matches any single character, otherwise case insensitive matching +//-------------------------------------------------------------------------------------------------- +bool GlobMatch( const char *pszGlob, const char *pszString ); + +//-------------------------------------------------------------------------------------------------- +// Processes #insert and #update KeyValues macros +// +// #insert inserts a new KeyValues file replacing the KeyValues #insert with the new file +// +// #update updates sibling KeyValues blocks subkeys with its subkeys, overwriting and adding +// KeyValues as necessary +//-------------------------------------------------------------------------------------------------- +KeyValues *HandleKeyValuesMacros( KeyValues *kv, KeyValues *pkvParent = nullptr ); \ No newline at end of file diff --git a/tier0/DESKey/ALGO.H b/tier0/DESKey/ALGO.H new file mode 100644 index 0000000..b0d6201 --- /dev/null +++ b/tier0/DESKey/ALGO.H @@ -0,0 +1,68 @@ + +#ifdef __cplusplus +extern "C" + { +#endif + +#ifndef APIENTRY +#define APIENTRY FAR PASCAL +#endif + +void APIENTRY DK2SetupAlgorithmString ( LPSTR String, WORD Cmd ); + +void APIENTRY DK2SetMaximumIterations( WORD MaxIter ); + +void APIENTRY DK2Sub_ReadRandomNumbers( WORD DataReg, + LPSTR Id, + LPSTR PKey, + WORD Seed, + LPSTR Buffer ); + +void APIENTRY DK2Sub_ReadMemory( WORD DataReg, + LPSTR Id, + LPSTR PKey, + WORD Seed, + WORD Address, + LPSTR Buffer ); + +void APIENTRY DK2Sub_WriteMemory( WORD DataReg, + LPSTR Id, + LPSTR PKey, + WORD Seed, + WORD Address, + WORD SecretCounter, + LPSTR Password, + LPSTR DUSN, + LPSTR Buffer ); + +void APIENTRY DK2Sub_ReadDownCounter( WORD DataReg, + LPSTR Id, + LPSTR PKey, + LPDWORD DownCounter ); + +void APIENTRY DK2Sub_SubtractDownCounter( WORD DataReg, + LPSTR Id, + LPSTR PKey, + DWORD SubValue, + LPDWORD DownCounter ); + +void APIENTRY DK2Sub_RestartDownCounter( WORD DataReg, + LPSTR Id, + LPSTR PKey, + WORD SecretCounter, + LPSTR Password, + LPSTR DUSN, + DWORD DownCounter ); + +void APIENTRY DK2Sub_AccessNormalCommands( WORD DataReg, + LPSTR Id, + LPSTR PKey, + WORD Disable ); + +void APIENTRY DK2Algorithm( WORD Iterations, + LPSTR AlgoStr, + LPSTR PrivKey ); + +#ifdef __cplusplus + } +#endif \ No newline at end of file diff --git a/tier0/DESKey/ALGO32.LIB b/tier0/DESKey/ALGO32.LIB new file mode 100644 index 0000000000000000000000000000000000000000..5ad9131e12de831c47d1d5b161d57eb14173a92d GIT binary patch literal 8878 zcmeHNe{5UT6~6va$8lWZ(v-Fks9~=tK$kjBng#@&(>O^>lhmOlPi6Ib7V+;+37^;Ru9Zii)Ev|u-x3}xY^ONsli#uwmX#jrlv-P z+G}BqtzgU;F1#vtGgfu%>s$eMyD!igO7@M!Mn?DfgV9uo1$t63Z*X*&1)}lMeF;t_ z#zG-5Anonz2y};HZSnp0;(mv-HM%dJ7)jn0b2T_Q!xu_+2k#k)rDEHYp+qn_5+5CN zHRYO8`|}-q$ppr9wTPzFV4!tqC^RZzwqIjvpLti>F2b zUEG9O#S{0soXS`LqzJO-5pLy0k$)1g5dO9m53Eud4Ki!U{pOazAto7K+!P&uRw z*r?7>!`_+-hcU+{^np24Q?ReU?#@m5bvIz)!$!t-S2A|2iaP<*GaWOO`O~3I2bGK0 zZIM;~|60O%zUEq%4;9PCqVT?0bT@C#h5cSj{eU+V4F$(S1K!pi_s;J6om+2bDswm% ziVY$D>f?hE4ZF3gb7xz(+sCCX8*^zxf#u^jZ*FMH4Ws;Ta5$Qqo9*@nrvo*Wy_PPW zneG1Gq0Nu#8M_J|N2Pu^5gJM!2#Gt!JRtgXxjW^*#m4Ff$Huf=IBQ86XO8LmWayrx zro{&OY2dx2VA4Ok&9k?DI5e2rCw1gRN$yoN{z)UZ&1TzLjAw`3JCONIk%KB_@vLa9 zB>@x?UkuLy{`HXYD{`b)x%nN2%&{W8Zw`mT!BjLE9T~hOl^PkY2VuV`vupd-wgRc0 zUGARN?slJjVLc|m9?TaS?lN2^Vk0$?243j+!_gr{pfln4PoTKfAENSoQN+(0m*fosemS8di;3j8g-O zaOpo|8+a{NgUB_wDp-AZB${);F9E*K=)PsZlZZ#)nPUUPcRL4s@o@5BFcBJPv%3=` z2axgy`bV$<92^^H^=u!2IR|L}X>{fyt4$2#8j8pE*N+U(t+OziwxPh^Y32qUc{~+k z`iB1qO6%)ne}k^n+XG_jYT$eFgtotAuEVN9BpKf ztcpW22DS{fb}YI#=E`KNTV`D?zT{G87>+k7s|H#X^!k z`Sb|7q6FCtZXt37M{c2>i=9gXBeDGfaatL=n;!yP*j@97l#R{?k-(HXmvPHwyiK`; z$1|RR?LB&E^A{MKJd;Xs#=9Q8AMh4UMH#OslaU=>)QAvSE>paZwvQEAvKVC^ZBi&7 z|9&|s+4|)}l2ty{GdV4vP1@u>$#&D*9T}q~t!wQ`eJY<0?8!$s4N|9!1h3-`MUi)D zqbk2O4bf0vC924(g+*&mnx;WV7Nzot^B?o7&*tJoU*cJXQAIuV+_NaR6rY7QHWm6u zZl5eZh3EFkO)bsJ&Pw~_8s^77`7+Yj4jKlf*lRrCy+sq!7V?_mU^1xTk)~2kC57)= zWS?}Q?Rv=3DUb?zO*!w{51Da|++zEh4gE8a`H4oZ9NyEAc}XL;7~VMWUWLpSN;fE! z8_MAwfy~2-T+aKot55x3!1Xv}dc?-YaPTdQ_Y7o?D{|%V;6Q8!GB47^P$=5V&94b5 z%1vnsr5xT9XzPK@zZ5wdz8v0-nEWlI07^N$X|%Z^W2E3jSqjeu8IK}Y4(|zI+yR-- z6}hF>!-ZcI9$Zw);q67+9gz90B1g+tPTvn9^TkE*XnjpqRGLx_?`6oHQe?}`ZypWj zATv#eA(Sg{mBV`sar+z{K~Qpd+U$EN&T0QN_<5FY{Nyg2E7%;3HF46IHg4&d__F%& z2bs?5OqXPn=1+C%-InaBPDA^tPNOf~SsmCjW2OThDMFVx8Qn$zD99H)hm-$Su(0fw zjNzxKVaA9~w92&LA{Z1?a2-iRoZlusw1uCfTYtYNH?65I$t=%DbiJH0<;LA1Ny_v| zb^RWHx=*@V@F5jp*lr{YYwwLPzgHko_x@hL5bBdG5eozyO5?;yYhcgBN!v_~IJT1K zh&hrDLc?o7(OnH|Moqfbu$`!{CEZ0TUvd=3U#azy&`%@s)HRiz$R8m|4jN+L%xrX3YP<|JquwLXvHZCz3=Eh47U)C;jDFBVRQ+e-Ie0eC^UhBJRbYKiqeQr_+1y`uurmk5QmC!U>P%c5_N*;(`Y1^fc4I&;)Qu4xmoN85%pTIB_E2&c*+Z12v)DH1D6A3$C9D$5^FXj)<07l%au?ZAYZYy3ModIZ z!YC0Fc{(L#`3x*#%bvH%=U|AHU~hm?Rv@%2p6mzaUbJ4xt%Egg;X!E61!0%eLP6Lk z8GBWaGI9&rAziESD4w`$^Uk1ZP1~{Xg{wh{2=IUs5kOk2aU?7;1)drUkK&H>$Bw1L zKi0@;)hmkiuTVk(4;i4U9Tt#0y|gv06)pf}pa5Fn^|fAvgpvig0(oyKMLF z$6g#y^dQ1$*FEZ%YBKG*OgD38r&86==rX31QM&Jf-a4K(ov;BsW6GR4H#a`3v&>A- zR-{ckdopiKE*zZ0Fg4q#d+J1BPo^rf%am!aaK4f0Hm5=Qbk3P{dqt*A?>v)fuT1kf zIJ4>Y%J1AcL+d3@)$||fhaW3QTWCUBIF9cNhr{w8{o(;m|EK4(pX=ornU~#3IsF<7?Pa)~9=P7t9fZU}25z8EmX*s7w1R;h7uK9Tsdf9eS@P-BTsJ z8~YHv+iz7;Ah4UlauxjB;}ANZ$52<&ya`KwM}F#7|4$M7IZoaR`uj4GB6q`2G`J1_xgRgkfnuU{)M*{yQ|a% zlg!&D-%Hl`^6e@W*n`Ed%Lgz|UUb^phReiHTa>p=jA}vA9w>IX5l|w=$3cl0|52Vg z4obxMnLPCuP&Azyrh}0M%1TfI#Q{p7xIl@%!=OZu$3b=QzVtk}9h8av3Y42u$3eAm zYC7La&w@oOJ@FN-5Mh7E9Tkkvy?iU^OZ&Vd|ID2R{YuX}C^u4h*149Btti?j0kkn1 zbbNaR^-xoDnj}P_%tM7*7ki560-qHAcs`A^rKrx&`d}i`?dYX_BF8vK`>c;fD10JU z?kOTACmP{(^Z0MnvFgF0aFbn&l+$RgUk!4iJG108COSt2plSvCCKF2g~*lsNs|_tegGSE z^Qg(^UwQtuE*1Ib;83Y5L8d%ih+I{IOn3&}6R2^jXa9cd?3jxDDmYY@mmpIN79uY% zL8c$Y2HiAjLOw8SyjDei7aS_pCCKCzg~-(<$arn7%$Zi%a+B|biu@TkR92KA(;J9F zG)U>;Q+MI#I%T zvw)+Goi*sHYC$-wxfXNiQS@e;J_o?t|FW<6m-sNC{LA{{H`g@8=y35z1Vu0XOFsqB zyqVX&hbWij3!<@>0!?2*Xkoe*0cni_8Xsr;53+mEZ~y=R literal 0 HcmV?d00001 diff --git a/tier0/DESKey/DK2WIN32.H b/tier0/DESKey/DK2WIN32.H new file mode 100644 index 0000000..440373d --- /dev/null +++ b/tier0/DESKey/DK2WIN32.H @@ -0,0 +1,366 @@ +/* + * $History: DK2WIN32.H $ + * + * ***************** Version 1 ***************** + * User: Alun Date: 1/07/99 Time: 11:31 + * Created in $/DK2/Software/C Drivers/Windows/API/DLL/DK2Win32 + * Initial version added to Source Safe version control + */ +#ifdef __cplusplus +extern "C" + { +#endif + +///////////////////////////////////////////////////////////////// +// Error Codes +// +// All codes are returned from DK2GetLastError and can be formated +// into a message string by calling DK2FormatError + +///////////////////////////////////////////////////////////////// +// DK2ERR_SUCCESS +// +// The command was successfull + +#define DK2ERR_SUCCESS 0x0000 + + +///////////////////////////////////////////////////////////////// +// DK2ERR_TOOMANUUSERS +// +// One or mode DK2 network servers were found but they were all +// full. + +#define DK2ERR_TOOMANYUSERS 0x0001 + +///////////////////////////////////////////////////////////////// +// DK2ERR_ACCESS_DENIED +// +// A DK2 network servers were found but access was denied, either +// due to user restrictions, or invalid login memory. +// See FindDK2Ex + +#define DK2ERR_ACCESS_DENIED 0x0002 + + +///////////////////////////////////////////////////////////////// +// DK2ERR_DESKEY_NOTFOUND +// +// A DK2 command failed because a DK2 was not found, either +// locally or accross the network + +#define DK2ERR_DESKEY_NOTFOUND 0x0003 + +///////////////////////////////////////////////////////////////// +// DK2ERR_NORESPONSE +// +// A DK2 oommand to a server failed becase the server did not +// respond. + +#define DK2ERR_NORESPONSE 0x0004 + +///////////////////////////////////////////////////////////////// +// DK2ERR_NOSERVERS +// +// The drivers searched for a DK2 a network server, but none were +// found. + +#define DK2ERR_NOSERVERS 0x0005 + +///////////////////////////////////////////////////////////////// +// DK2ERR_DRIVERNOTINSTALLED +// +// The DK2 drivers are not installed + +#define DK2ERR_DRIVERNOTINSTALLED 0x0006 + +///////////////////////////////////////////////////////////////// +// DK2ERR_COMMANDNOTSUPPORTED +// +// The DK2 does not support the requested command + +#define DK2ERR_COMMANDNOTSUPPORTED 0x0007 + +///////////////////////////////////////////////////////////////// +// DK2ERR_ALREADYNETWORK +// +// A local DK2 command failed because there is a server runnning +// the command must be carried out over the network. + +#define DK2ERR_ALREADYNETWORK 0x1001 + +///////////////////////////////////////////////////////////////// +// DK2ERR_COMMANDNOTNETWORK +// +// A DK2 command failed because the command cannot operate over +// the network + +#define DK2ERR_COMMANDNOTNETWORK 0x1002 + +///////////////////////////////////////////////////////////////// +// DK2ERR_TOOMANYPROGS +// +// The maximum possible programs using the DK2 drivers has been +// reached + +#define DK2ERR_TOOMANYPROGS 0x1004 + +///////////////////////////////////////////////////////////////// +// DK2ERR_BADOS +// +// The DK2 command will not function on the current operating system + +#define DK2ERR_BADOS 0x1005 + +///////////////////////////////////////////////////////////////// +// DK2ERR_NETWORKONLY +// +// The DK2 command failed because it can only be performed across +// the network and a local connection was specified + +#define DK2ERR_NETWORKONLY 0x1006 + +///////////////////////////////////////////////////////////////// +// DK2ERR_CANCELLED +// +// Returned the GDI/ECP Window is cancelled + +#define DK2ERR_CANCELLED 0x1007 + +///////////////////////////////////////////////////////////////// +// DK2ERR_FAILURE +// +// The command failed due to an error comunicating with the protocol + +#define DK2ERR_FAILURE 0x8000 + +//////////////////////////////////////////////////////////////// +// DK2ERR_PROTOCOLFAILURE +// +// The DK2 command faied due to a problem in the protocol + +#define DK2ERR_PROTOCOLFAILURE 0x8001 + +//////////////////////////////////////////////////////////////// +// DK2ERR_BADPARAMETER +// +// The DK2 command failed due to an invalid parameter passed to the +// function + +#define DK2ERR_BADPARAMETER 0x8002 + +//////////////////////////////////////////////////////////////// +// DK2ERR_NOMEMORY +// +// The DK2 command failed because the function could not allocate +// enough memory + +#define DK2ERR_NOMEMORY 0x8003 + +//////////////////////////////////////////////////////////////// +// DK2ERR_STARTPROTOCOL +// +// The DK2 command failed because the current protocol did not +// start + +#define DK2ERR_STARTPROTOCOL 0x8004 + +//////////////////////////////////////////////////////////////// +// DK2ERR_NOPROTOCOL +// +// The DK2 command failed be cause the current protocol does not +// exist or is not loaded + +#define DK2ERR_NOPROTOCOL 0x8005 + +//////////////////////////////////////////////////////////////// +// DK2ERR_NOSERVERMEMORY +// +// The DK2 command failed because the server could not allocate +// enough memory + +#define DK2ERR_NOSERVERMEMORY 0x8006 + +//////////////////////////////////////////////////////////////// +// DK2ERR_INVALIDCONNECTION +// +// The DK2 command failed because the specified connection is +// invalid + +#define DK2ERR_INVALIDCONNECTION 0x8007 + + +//////////////////////////////////////////////////////////////// +// Structures +//////////////////////////////////////////////////////////////// + +#pragma pack( 1 ) + +#define DK2MEMORYMAP +typedef struct _tDK2Memory +{ + WORD wAddress; + WORD wSeed; + WORD wCount; + LPSTR lpBytes; + WORD wModule; +} DK2MEMORY, FAR *LPDK2MEMORY; + +typedef struct _tDateTime +{ + WORD wDay; + WORD wMonth; + WORD wYear; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; +} DATETIME, *NPDATETIME, FAR *LPDATETIME; + +#pragma pack() + + +//////////////////////////////////////////////////////////////// +// DK2 Functions +//////////////////////////////////////////////////////////////// + +BOOL APIENTRY DK2DriverInstalled( void ); + +WORD APIENTRY FindDK2( LPSTR Id, LPSTR PKey ); + +WORD APIENTRY FindDK2Ex( LPSTR Id, LPSTR PKey, LPDK2MEMORY lpDK2Memory ); + +WORD APIENTRY FindDK2ExP( LPSTR Id, LPSTR PKey, WORD Address, WORD Seed, WORD Count, LPSTR Bytes, WORD Module ); + +void APIENTRY DK2LogoutFromServer( WORD DataReg ); + +WORD APIENTRY DK2FindLPTPort( WORD Port ); + +WORD APIENTRY DK2GetDelayTime ( void ); + +void APIENTRY DK2SetDelayTime( WORD Delay ); + +void APIENTRY DK2ReadRandomNumbers( WORD DataReg, + LPSTR Id, + WORD Seed, + LPSTR Buffer, + WORD BytesToRead ); + +void APIENTRY DK2ThroughEncryption( WORD DataReg, + LPSTR Id, + WORD Seed, + LPSTR Buffer, + WORD BytesToEncrypt ); + + +void APIENTRY DK2ReadMemory( WORD DataReg, + LPSTR Id, + WORD Seed, + WORD Address, + LPSTR Buffer, + WORD BytesToRead ); + +void APIENTRY DK2WriteMemory( WORD DataReg, + LPSTR Id, + WORD Seed, + WORD Address, + LPSTR Buffer, + WORD BytesToWrite, + LPSTR Password ); + +void APIENTRY DK2ReadDownCounter( WORD DataReg, + LPSTR Id, + DWORD *DownCounter ); + + +void APIENTRY DK2DecrementDownCounter( WORD DataReg, + LPSTR Id ); + +BOOL APIENTRY DK2RegisterModule( WORD DataReg, WORD wModule ); + +BOOL APIENTRY DK2UnregisterModule( WORD DataReg, WORD wModule ); + +void APIENTRY DK2RestartDownCounter( WORD DataReg, + LPSTR Id, + DWORD NewCounter, + LPSTR Password ); + +void APIENTRY DK2ReadDUSN( WORD DataReg, + LPSTR Id, + LPSTR Password, + LPWORD SecCount, + LPSTR DUSN ); + +void APIENTRY DK2SendAlgorithmString( WORD DataReg, + LPSTR Id, + WORD Iteration1, + WORD Iteration2, + LPSTR Buffer1, + LPSTR Buffer2 ); + +void APIENTRY DK2SendAlgorithmBuffer( WORD DataReg, + LPSTR Id, + LPWORD Iteration, + LPSTR Buffer, + WORD BufferCount ); + +void APIENTRY DK2SendAndReceive( WORD DataReg, LPSTR Id, LPSTR lpFirst, WORD wFirst, LPSTR lpSend, WORD wSend, LPSTR lpReceive, WORD wReceive, WORD wCount ); + +BOOL APIENTRY DK2Success( void ); + +void APIENTRY DK2AllowChangeInterrupts( WORD Change ); + +WORD APIENTRY DK2DetectSpeed( WORD DataReg, + LPSTR Id ); + +WORD APIENTRY DK2SubDetectSpeed( WORD DataReg, + LPSTR Id, + LPSTR PKey, + LPSTR Bytes ); + +//////////////////////////////////////////////////////////////// +// time function +void APIENTRY DK2GetSystemTime( WORD DateReg, LPSTR Id, LPDATETIME lpDateTime ); + +//////////////////////////////////////////////////////////////// +// DK2 Flags + +#define DK2_BITRONICS 0x00000001 +#define DK2_HASBITRONICS 0x00000002 + +DWORD APIENTRY DK2GetFlags ( WORD DataReg, + LPSTR Id ); + +VOID APIENTRY DK2SetFlags ( WORD DataReg, + LPSTR Id, + DWORD Flags ); + +//////////////////////////////////////////////////////////////// + +WORD APIENTRY DK2Encode( LPSTR lpszData, + WORD cbData, + LPSTR lpszEncode, + WORD cbEncode ); + +WORD APIENTRY DK2Decode( LPSTR lpszData, + LPSTR lpszDecode ); + +//////////////////////////////////////////////////////////////// +// DK2 Access Flags - Override Searching Network or Local + +#define DNET_NETWORK 0x0001 +#define DNET_LOCAL 0x0002 + +void APIENTRY DK2SetAccessFlags( WORD wFlags ); + +//////////////////////////////////////////////////////////////// + +DWORD APIENTRY DK2GetLastError( void ); + +void APIENTRY DK2FormatError( DWORD Error, LPSTR ErrorString, int MaxLen ); + +WORD APIENTRY DK2GetServerName( WORD DataReg, LPSTR lpszServerName, LPSTR lpszComputerName ); + +#ifdef __cplusplus + } +#endif diff --git a/tier0/DESKey/DK2WIN32.LIB b/tier0/DESKey/DK2WIN32.LIB new file mode 100644 index 0000000000000000000000000000000000000000..eaac27da5336cf13a9b98ba7d6b7acb1e06347c6 GIT binary patch literal 24300 zcmd^HOK%)i7XI80$>2Z;AqgQ5Vkg0q2`F(tYdoQ}KVHuNJd zmob)*C`GfJ&18XC%wmKPE0!#?$chDGjaaZ4vEVnbB6CjNdfZ!myW+N5qg+zb*N=10 zt;hZDIrY`8TQzjKVz1P$3|tyim%?a%bZji2%cewuzXOcn9|SJ_2r$xL3S=FCF*+%b{}o_dejqUQ6Tq1M zTHu-uFlI^uHx2-dxo-vL?*oj}w2zEq?+Kj$9AKOs5m>kmFfQ&9xKITc#kU25v1xKa~%D+e&%{Z!!AHvnUj+H!JWd3omM{5uPa%cbe^ z)WXdC;yVk=r5l;8wPrSh<>k4h+1Yjvd=|I}hsEnlb2pYt6N?kz$`kc^Yip`jX|7r` zO~UHuSkvGs@Mm4UkHLJ7pi}NkpQH~{MC(mn^Q?lxn?Zvf*rK~t! zAbhXZE2|sI?vh_O{&hXv(}AD=XHZr4o4QJZ6Tefnb@dEO_+OiuJ-&JYqc!9r@)1MS0H`PXKn{BPOHl3?>t5LRW ziex1ub%kPYh4Wm60yA22PQneL{EtIqgWC7FIcN<8>#1>av_?bT;9= zzGd0#TZMEcTl+AV-txS4Hm?Px7@pj`dzXk(l9&?1l0#6pSzWMJEK1eIteiExYHAtP zpiWobCIT8>;%(zsSJY~*7V?GdRyOC1wwhIM=X5r4 zd#6-!DmN<|A1Pgv6HsLDUI~qbO6X0JLc^p3zCR3naRj(e8sW@w;I|XNcc*|mL%{Fn zfFFi|A1?u4rU55Q=loIN*F5mxGVs$B@bxrcUjs@rz=0dUw{x_vd0OXb;JstO=jVZu zv%vKQV9!OMdV%ysku=vh@C#{=vRNTh9Pd16u5IW!Zo$ZLe04x&yvodTJZ!)ChF>n#1JM~ zG-xs~$A_0J!~7?z#e#;69TMnHBnQ>rse15S<0;cK?no18(uGDY(E9Y*qCLBNu5x(# z@*rwyjbh+R?b^VzKKWcvZn`{A=7dvunDD(*SoqIBC+tSfM>_Gz(mm?@p}6c60+K_d zoWk=K9i%24!a7n=gtk~{MD|Y7A|57T4JrtOMK*R+GqJ#t>%_t*EYby0Sg3@OYK_Ts zr7%!^vKHZ0wQFywT0F$0B5k0B<(|C!amTD=WDTs34N- z(H7bRQz{F?GqmXycUY_o)vz!gu%f_^&PYLr;VS9R&?d{e&iYHIIKAduNMdZl7{Na6vU!5!dA4)E+RY_2u%LJPuIfxP+|KAC` z@V*x@^2qla^NjRCLsj@#@?(1ugOB(-zPvyNUe7+SfLm&x*96abzhuT}9V@NIJ=)mg z!995VSk#MJmv<_KV4sV?Bev86i???~25rsv$*~U^BA?O7!uBEl@E5L0>eTPNMEPgz2hyH zPD{M?d%S%`Ti{`6TKXbN;n8Bl$Tzn=|n2htNzdO@QR!7Xpx|HKw zh8e7$3}chCZXw31dV!umX4knkHzI4Fr#D9FT;?*&U~^Dm^KUAf?W2rI`nPLFvxRXZ zRMAmllVJvRgiqD!U#D%8i&2ozD}G3^TY)icXKq_za;0O1r&I$r&>C zPWLNah8c_|jR-NL^0cl5GItT5W?XgdCngzYuz5Djw0!m~w&hyQWV{4YyN^&au2^U$ zhRZO6)1)hw_J=omJI-f@9-=v5F2f8y&nYoF5sOj%wAX|n?LPAwGBbhN$z_Duc_Jt2l-hmv9Wg^A z(Wtk}FoV$~jnw`$RT80H=UqdtN@?b~%P@n_q^nYX(k6ONHF5s@E~3+nJKOYxgv&64 z&7?bMZO={h@a$>Ic<6ul2KFU zYQ9WtGR$B!X|AU9j+Z0}w2Rm@V=R|mN#QcgU@~bex5MMW3E|njt~KNS87Y64VFss3 z_s_J?aQBo^Gqmk1#3sWGMw7H{+k^K#WHVbZV;tcyG08B4%_GXl5I?x?&kmE{me5lc z&3ImgG{4I*gT;;-D14oTc*R8mkzH#bQ)W?<;&&NlFxhbyb;#tC-!0U=^2r)83+xQB z$S{LRe)~bNr0`ez>1S5m8(n&Qbz%me1>%xnIG?(=@jR1!=7~#&;e6`er1VVkN!Q~p a!*D)zZ^3#d`J`)cmtj1g?eEh~xZFUR* literal 0 HcmV?d00001 diff --git a/tier0/InterlockedCompareExchange128.masm b/tier0/InterlockedCompareExchange128.masm new file mode 100644 index 0000000..4f8025c --- /dev/null +++ b/tier0/InterlockedCompareExchange128.masm @@ -0,0 +1,78 @@ +; call cpuid with args in eax, ecx +; store eax, ebx, ecx, edx to p +PUBLIC _InterlockedCompareExchange128 +.CODE + + +_InterlockedCompareExchange128 PROC FRAME +; extern "C" unsigned char _InterlockedCompareExchange128( int64 volatile * Destination, int64 ExchangeHigh, int64 ExchangeLow, int64 * ComparandResult ); + .endprolog + + ; fastcall conventions: + ; RCX = Destination + ; RDX = Exchange High + ; R8 = Exchange Lo + ; R9 = ComparandResult + + ; CMPXCHG16B refernece: + ; http://download.intel.com/design/processor/manuals/253666.pdf + + ; Stash RBX in R11 + mov r11, rbx + + ; Destination ptr to r10 + mov r10, rcx + + ; RCX:RBX Exchange + mov rcx, rdx + mov rbx, r8 + + ; RDX:RAX Comparand + mov rax, [r9] + mov rdx, [r9+8] + + ; Do the atomic operation + lock cmpxchg16b [r10] + + ; RDX:RAX now contains the value of the destination, before the atomic operation. + ; (Either it already matched and was not changed, or it did not match, in which case + ; the value has been loaded into RDX:RAX.) The _InterlockedCompareExchange128 intrinsic + ; semantics specify that this is always written out to CompareResult, so give it + ; back to the caller. + mov [r9], rax + mov [r9+8], rdx + + ; Return value is in AL, set it equal to the zero flag + setz al + + ; Restore RBX and get out + mov rbx, r11 + ret + +_InterlockedCompareExchange128 ENDP + +; For reference, here's what VC2010 generated +; +; __declspec(noinline) unsigned char Test_InterlockedCompareExchange128( int64 volatile * Destination, int64 ExchangeHigh, int64 ExchangeLow, int64 * ComparandResult ) +; { +; return _InterlockedCompareExchange128( Destination, ExchangeHigh, ExchangeLow, ComparandResult ); +; } +; +; +;?Test_InterlockedCompareExchange128@GCSDK@@YAEPEC_J_J1PEA_J@Z (unsigned char __cdecl GCSDK::Test_InterlockedCompareExchange128(__int64 volatile *,__int64,__int64,__int64 *)): +; 0000000000000000: 48 89 5C 24 08 mov qword ptr [rsp+8],rbx +; 0000000000000005: 49 8B 01 mov rax,qword ptr [r9] +; 0000000000000008: 4C 8B D1 mov r10,rcx +; 000000000000000B: 48 8B CA mov rcx,rdx +; 000000000000000E: 49 8B 51 08 mov rdx,qword ptr [r9+8] +; 0000000000000012: 49 8B D8 mov rbx,r8 +; 0000000000000015: F0 49 0F C7 0A lock cmpxchg16b oword ptr [r10] +; 000000000000001A: 48 8B 5C 24 08 mov rbx,qword ptr [rsp+8] +; 000000000000001F: 49 89 01 mov qword ptr [r9],rax +; 0000000000000022: 49 89 51 08 mov qword ptr [r9+8],rdx +; 0000000000000026: 0F 94 C0 sete al +; 0000000000000029: C3 ret + + +_TEXT ENDS +END diff --git a/tier0/PMELib.cpp b/tier0/PMELib.cpp new file mode 100644 index 0000000..9e15086 --- /dev/null +++ b/tier0/PMELib.cpp @@ -0,0 +1,665 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#ifdef _WIN32 +#include + +#pragma warning( disable : 4530 ) // warning: exception handler -GX option + +#include "tier0/valve_off.h" +#include "tier0/pmelib.h" +#if _MSC_VER >=1300 +#else +#include "winioctl.h" +#endif +#include "tier0/valve_on.h" + +#include "tier0/ioctlcodes.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + + +PME* PME::_singleton = 0; + +// Single interface. +PME* PME::Instance() +{ + if (_singleton == 0) + { + _singleton = new PME; + } + return _singleton; +} + +//--------------------------------------------------------------------------- +// Open the device driver and detect the processor +//--------------------------------------------------------------------------- +HRESULT PME::Init( void ) +{ + OSVERSIONINFO OS; + + if ( bDriverOpen ) + return E_DRIVER_ALREADY_OPEN; + + switch( vendor ) + { + case INTEL: + case AMD: + break; + default: + bDriverOpen = FALSE; // not an Intel or Athlon processor so return false + return E_UNKNOWN_CPU_VENDOR; + } + + //----------------------------------------------------------------------- + // Get the operating system version + //----------------------------------------------------------------------- + OS.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + GetVersionEx( &OS ); + + if ( OS.dwPlatformId == VER_PLATFORM_WIN32_NT ) + { + hFile = CreateFile( // WINDOWS NT + "\\\\.\\GDPERF", + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + else + { + hFile = CreateFile( // WINDOWS 95 + "\\\\.\\GDPERF.VXD", + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + + if (hFile == INVALID_HANDLE_VALUE ) + return E_CANT_OPEN_DRIVER; + + + bDriverOpen = TRUE; + + + //------------------------------------------------------------------- + // We have successfully opened the device driver, get the family + // of the processor. + //------------------------------------------------------------------- + + + + //------------------------------------------------------------------- + // We need to write to counter 0 on the pro family to enable both + // of the performance counters. We write to both so they start in a + // known state. For the pentium this is not necessary. + //------------------------------------------------------------------- + if (vendor == INTEL && version.Family == PENTIUMPRO_FAMILY) + { + SelectP5P6PerformanceEvent(P6_CLOCK, 0, TRUE, TRUE); + SelectP5P6PerformanceEvent(P6_CLOCK, 1, TRUE, TRUE); + } + + return S_OK; + + +} + + + +//--------------------------------------------------------------------------- +// Close the device driver +//--------------------------------------------------------------------------- +HRESULT PME::Close(void) +{ + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + bDriverOpen = false; + + if (hFile) // if we have no driver handle, return FALSE + { + BOOL result = CloseHandle(hFile); + + hFile = NULL; + return result ? S_OK : HRESULT_FROM_WIN32( GetLastError() ); + } + else + return E_DRIVER_NOT_OPEN; + + +} + +//--------------------------------------------------------------------------- +// Select the event to monitor with counter 0 +// +HRESULT PME::SelectP5P6PerformanceEvent(uint32 dw_event, uint32 dw_counter, + bool b_user, bool b_kernel) +{ + HRESULT hr = S_OK; + + if (dw_counter>1) // is the counter valid + return E_BAD_COUNTER; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + if ( ((dw_event>>28)&0xF) != (uint32)version.Family) + { + return E_ILLEGAL_OPERATION; // this operation is not for this processor + } + + if ( (((dw_event & 0x300)>>8) & (dw_counter+1)) == 0 ) + { + return E_ILLEGAL_OPERATION; // this operation is not for this counter + } + + switch(version.Family) + { + case PENTIUM_FAMILY: + { + uint64 i64_cesr; + int i_kernel_bit,i_user_bit; + BYTE u1_event = (BYTE)((dw_event & (0x3F0000))>>16); + + if (dw_counter==0) // the kernel and user mode bits depend on + { // counter being used. + i_kernel_bit = 6; + i_user_bit = 7; + } + else + { + i_kernel_bit = 22; + i_user_bit = 23; + } + + ReadMSR(0x11, &i64_cesr); // get current P5 event select (cesr) + + // top 32bits of cesr are not valid so ignore them + i64_cesr &= ((dw_counter == 0)?0xffff0000:0x0000ffff); + WriteMSR(0x11,i64_cesr); // stop the counter + WriteMSR((dw_counter==0)?0x12:0x13,0ui64); // clear the p.counter + + // set the user and kernel mode bits + i64_cesr |= ( b_user?(1<<7):0 ) | ( b_kernel?(1<<6):0 ); + + // is this the special P5 value that signals count clocks?? + if (u1_event == 0x3f) + { + WriteMSR(0x11, i64_cesr|0x100); // Count clocks + } + else + { + WriteMSR(0x11, i64_cesr|u1_event); // Count events + } + + } + break; + + case PENTIUMPRO_FAMILY: + + { + BYTE u1_event = (BYTE)((dw_event & (0xFF0000))>>16); + BYTE u1_mask = (BYTE)((dw_event & 0xFF)); + + // Event select 0 and 1 are identical. + hr = WriteMSR((dw_counter==0)?0x186:0x187, + + + uint64((u1_event | (b_user?(1<<16):0) | (b_kernel?(1<<17):0) | (1<<22) | (1<<18) | (u1_mask<<8)) ) + ); + } + break; + + case PENTIUM4_FAMILY: + // use the p4 path + break; + + default: + return E_UNKNOWN_CPU; + } + + return hr; +} + +//--------------------------------------------------------------------------- +// Read model specific register +//--------------------------------------------------------------------------- +HRESULT PME::ReadMSR(uint32 dw_reg, int64 * pi64_value) +{ + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + BOOL result = DeviceIoControl + ( + hFile, // Handle to device + (DWORD) IOCTL_READ_MSR, // IO Control code for Read + &dw_reg, // Input Buffer to driver. + sizeof(uint32), // Length of input buffer. + pi64_value, // Output Buffer from driver. + sizeof(int64), // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in output buffer. + NULL // NULL means wait till op. completes + ); + + HRESULT hr = result ? S_OK : HRESULT_FROM_WIN32( GetLastError() ); + if (hr == S_OK && dw_ret_len != sizeof(int64)) + hr = E_BAD_DATA; + + return hr; +} + +HRESULT PME::ReadMSR(uint32 dw_reg, uint64 * pi64_value) +{ + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + BOOL result = DeviceIoControl + ( + hFile, // Handle to device + (DWORD) IOCTL_READ_MSR, // IO Control code for Read + &dw_reg, // Input Buffer to driver. + sizeof(uint32), // Length of input buffer. + pi64_value, // Output Buffer from driver. + sizeof(uint64), // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in output buffer. + NULL // NULL means wait till op. completes + ); + + HRESULT hr = result ? S_OK : HRESULT_FROM_WIN32( GetLastError() ); + if (hr == S_OK && dw_ret_len != sizeof(uint64)) + hr = E_BAD_DATA; + + return hr; +} + +//--------------------------------------------------------------------------- +// Write model specific register +//--------------------------------------------------------------------------- +HRESULT PME::WriteMSR(uint32 dw_reg, const int64 & i64_value) +{ + DWORD dw_buffer[3]; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + dw_buffer[0] = dw_reg; // setup the 12 byte input + *((int64*)(&dw_buffer[1]))= i64_value; + + BOOL result = DeviceIoControl + ( + hFile, // Handle to device + (DWORD) IOCTL_WRITE_MSR, // IO Control code for Read + dw_buffer, // Input Buffer to driver. + 12, // Length of Input buffer + NULL, // Buffer from driver, None for WRMSR + 0, // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in DataBuffer. + NULL // NULL means wait till op. completes. + ); + + HRESULT hr = result ? S_OK : HRESULT_FROM_WIN32( GetLastError() ); + if (hr == S_OK && dw_ret_len != 0) + hr = E_BAD_DATA; + + return hr; +} + + + +HRESULT PME::WriteMSR(uint32 dw_reg, const uint64 & i64_value) +{ + DWORD dw_buffer[3]; + DWORD dw_ret_len; + + if (bDriverOpen == false) // driver is not going + return E_DRIVER_NOT_OPEN; + + dw_buffer[0] = dw_reg; // setup the 12 byte input + *((uint64*)(&dw_buffer[1]))= i64_value; + + BOOL result = DeviceIoControl + ( + hFile, // Handle to device + (DWORD) IOCTL_WRITE_MSR, // IO Control code for Read + dw_buffer, // Input Buffer to driver. + 12, // Length of Input buffer + NULL, // Buffer from driver, None for WRMSR + 0, // Length of output buffer in bytes. + &dw_ret_len, // Bytes placed in DataBuffer. + NULL // NULL means wait till op. completes. + ); + + //E_POINTER + HRESULT hr = result ? S_OK : HRESULT_FROM_WIN32( GetLastError() ); + if (hr == S_OK && dw_ret_len != 0) + hr = E_BAD_DATA; + + return hr; +} + + + + + + + + + + + + + +#pragma hdrstop + + + + +//--------------------------------------------------------------------------- +// Return the frequency of the processor in Hz. +// + +double PME::GetCPUClockSpeedFast(void) +{ + int64 i64_perf_start, i64_perf_freq, i64_perf_end; + int64 i64_clock_start,i64_clock_end; + double d_loop_period, d_clock_freq; + + //----------------------------------------------------------------------- + // Query the performance of the Windows high resolution timer. + //----------------------------------------------------------------------- + QueryPerformanceFrequency((LARGE_INTEGER*)&i64_perf_freq); + + //----------------------------------------------------------------------- + // Query the current value of the Windows high resolution timer. + //----------------------------------------------------------------------- + QueryPerformanceCounter((LARGE_INTEGER*)&i64_perf_start); + i64_perf_end = 0; + + //----------------------------------------------------------------------- + // Time of loop of 250000 windows cycles with RDTSC + //----------------------------------------------------------------------- + RDTSC(i64_clock_start); + while(i64_perf_endSetProcessPriority(ProcessPriorityHigh); + + // wait for millisecond boundary + start_ms = GetTickCount() + 5; + while (start_ms <= GetTickCount()); + + // read timestamp (you could use QueryPerformanceCounter in hires mode if you want) +#ifdef COMPILER_MSVC64 + RDTSC(start_tsc); +#else + __asm + { + rdtsc + mov dword ptr [start_tsc+0],eax + mov dword ptr [start_tsc+4],edx + } +#endif + + // wait for end + stop_ms = start_ms + 1000; // longer wait gives better resolution + while (stop_ms > GetTickCount()); + + // read timestamp (you could use QueryPerformanceCounter in hires mode if you want) +#ifdef COMPILER_MSVC64 + RDTSC(stop_tsc); +#else + __asm + { + rdtsc + mov dword ptr [stop_tsc+0],eax + mov dword ptr [stop_tsc+4],edx + } +#endif + + + // normalize priority + pme->SetProcessPriority(ProcessPriorityNormal); + + // return clock speed + // optionally here you could round to known clocks, like speeds that are multimples + // of 100, 133, 166, etc. + m_CPUClockSpeed = ((stop_tsc - start_tsc) * 1000.0) / (double)(stop_ms - start_ms); + return m_CPUClockSpeed; + +} + + + +const unsigned short cccr_escr_map[NCOUNTERS][8] = +{ + { + 0x3B2, + 0x3B4, + 0x3AA, + 0x3B6, + 0x3AC, + 0x3C8, + 0x3A2, + 0x3A0, + }, + { + 0x3B2, + 0x3B4, + 0x3AA, + 0x3B6, + 0x3AC, + 0x3C8, + 0x3A2, + 0x3A0, + }, + { + 0x3B3, + 0x3B5, + 0x3AB, + 0x3B7, + 0x3AD, + 0x3C9, + 0x3A3, + 0x3A1, + }, + { + 0x3B3, + 0x3B5, + 0x3AB, + 0x3B7, + 0x3AD, + 0x3C9, + 0x3A3, + 0x3A1, + }, + { + + 0x3C0, + 0x3C4, + 0x3C2, + }, + { + 0x3C0, + 0x3C4, + 0x3C2, + }, + { + 0x3C1, + 0x3C5, + 0x3C3, + }, + { + 0x3C1, + 0x3C5, + 0x3C3, + }, + { + 0x3A6, + 0x3A4, + 0x3AE, + 0x3B0, + 0, + 0x3A8, + }, + { + 0x3A6, + 0x3A4, + 0x3AE, + 0x3B0, + 0, + 0x3A8, + }, + { + + 0x3A7, + 0x3A5, + 0x3AF, + 0x3B1, + 0, + 0x3A9, + }, + { + + 0x3A7, + 0x3A5, + 0x3AF, + 0x3B1, + 0, + 0x3A9, + }, + { + + 0x3BA, + 0x3CA, + 0x3BC, + 0x3BE, + 0x3B8, + 0x3CC, + 0x3E0, + }, + { + + 0x3BA, + 0x3CA, + 0x3BC, + 0x3BE, + 0x3B8, + 0x3CC, + 0x3E0, + }, + { + + 0x3BB, + 0x3CB, + 0x3BD, + 0, + 0x3B9, + 0x3CD, + 0x3E1, + }, + { + + + 0x3BB, + 0x3CB, + 0x3BD, + 0, + 0x3B9, + 0x3CD, + 0x3E1, + }, + { + 0x3BA, + 0x3CA, + 0x3BC, + 0x3BE, + 0x3B8, + 0x3CC, + 0x3E0, + }, + { + + 0x3BB, + 0x3CB, + 0x3BD, + 0, + 0x3B9, + 0x3CD, + 0x3E1, + }, +}; + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void PME::Validate( CValidator &validator, tchar *pchName ) +{ + validator.Push( _T("PME"), this, pchName ); + + validator.ClaimMemory( this ); + + validator.ClaimMemory( cache ); + + validator.ClaimMemory( ( void * ) vendor_name.c_str( ) ); + validator.ClaimMemory( ( void * ) brand.c_str( ) ); + + validator.Pop( ); +} +#endif // DBGFLAG_VALIDATE + +#pragma warning( default : 4530 ) // warning: exception handler -GX option +#endif + diff --git a/tier0/ValveETWProviderEvents.h b/tier0/ValveETWProviderEvents.h new file mode 100644 index 0000000..c1a575e --- /dev/null +++ b/tier0/ValveETWProviderEvents.h @@ -0,0 +1,1700 @@ +//**********************************************************************` +//* This is an include file generated by Message Compiler. *` +//* *` +//* Copyright (c) Microsoft Corporation. All Rights Reserved. *` +//**********************************************************************` +#pragma once +#include +#include +#include "evntprov.h" +// +// Initial Defs +// +#if !defined(ETW_INLINE) +#define ETW_INLINE DECLSPEC_NOINLINE __inline +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION +#if !defined(McGenDebug) +#define McGenDebug(a,b) +#endif + + +#if !defined(MCGEN_TRACE_CONTEXT_DEF) +#define MCGEN_TRACE_CONTEXT_DEF +typedef struct _MCGEN_TRACE_CONTEXT +{ + TRACEHANDLE RegistrationHandle; + TRACEHANDLE Logger; + ULONGLONG MatchAnyKeyword; + ULONGLONG MatchAllKeyword; + ULONG Flags; + ULONG IsEnabled; + UCHAR Level; + UCHAR Reserve; +} MCGEN_TRACE_CONTEXT, *PMCGEN_TRACE_CONTEXT; +#endif + +#if !defined(MCGEN_EVENT_ENABLED_DEF) +#define MCGEN_EVENT_ENABLED_DEF +FORCEINLINE +BOOLEAN +McGenEventEnabled( + __in PMCGEN_TRACE_CONTEXT EnableInfo, + __in PCEVENT_DESCRIPTOR EventDescriptor + ) +{ + // + // Check if the event Level is lower than the level at which + // the channel is enabled. + // If the event Level is 0 or the channel is enabled at level 0, + // all levels are enabled. + // + + if ((EventDescriptor->Level <= EnableInfo->Level) || // This also covers the case of Level == 0. + (EnableInfo->Level == 0)) { + + // + // Check if Keyword is enabled + // + + if ((EventDescriptor->Keyword == (ULONGLONG)0) || + ((EventDescriptor->Keyword & EnableInfo->MatchAnyKeyword) && + ((EventDescriptor->Keyword & EnableInfo->MatchAllKeyword) == EnableInfo->MatchAllKeyword))) { + return TRUE; + } + } + + return FALSE; + +} +#endif + + +// +// EnableCheckMacro +// +#ifndef MCGEN_ENABLE_CHECK +#define MCGEN_ENABLE_CHECK(Context, Descriptor) (Context.IsEnabled && McGenEventEnabled(&Context, &Descriptor)) +#endif + +#if !defined(MCGEN_CONTROL_CALLBACK) +#define MCGEN_CONTROL_CALLBACK + +DECLSPEC_NOINLINE __inline +VOID +__stdcall +McGenControlCallbackV2( + __in LPCGUID SourceId, + __in ULONG ControlCode, + __in UCHAR Level, + __in ULONGLONG MatchAnyKeyword, + __in ULONGLONG MatchAllKeyword, + __in_opt PEVENT_FILTER_DESCRIPTOR FilterData, + __inout_opt PVOID CallbackContext + ) +/*++ + +Routine Description: + + This is the notification callback for Vista. + +Arguments: + + SourceId - The GUID that identifies the session that enabled the provider. + + ControlCode - The parameter indicates whether the provider + is being enabled or disabled. + + Level - The level at which the event is enabled. + + MatchAnyKeyword - The bitmask of keywords that the provider uses to + determine the category of events that it writes. + + MatchAllKeyword - This bitmask additionally restricts the category + of events that the provider writes. + + FilterData - The provider-defined data. + + CallbackContext - The context of the callback that is defined when the provider + called EtwRegister to register itself. + +Remarks: + + ETW calls this function to notify provider of enable/disable + +--*/ +{ + PMCGEN_TRACE_CONTEXT Ctx = (PMCGEN_TRACE_CONTEXT)CallbackContext; +#ifndef MCGEN_PRIVATE_ENABLE_CALLBACK_V2 + UNREFERENCED_PARAMETER(SourceId); + UNREFERENCED_PARAMETER(FilterData); +#endif + + if (Ctx == NULL) { + return; + } + + switch (ControlCode) { + + case EVENT_CONTROL_CODE_ENABLE_PROVIDER: + Ctx->Level = Level; + Ctx->MatchAnyKeyword = MatchAnyKeyword; + Ctx->MatchAllKeyword = MatchAllKeyword; + Ctx->IsEnabled = EVENT_CONTROL_CODE_ENABLE_PROVIDER; + break; + + case EVENT_CONTROL_CODE_DISABLE_PROVIDER: + Ctx->IsEnabled = EVENT_CONTROL_CODE_DISABLE_PROVIDER; + Ctx->Level = 0; + Ctx->MatchAnyKeyword = 0; + Ctx->MatchAllKeyword = 0; + break; + + default: + break; + } + +#ifdef MCGEN_PRIVATE_ENABLE_CALLBACK_V2 + // + // Call user defined callback + // + MCGEN_PRIVATE_ENABLE_CALLBACK_V2( + SourceId, + ControlCode, + Level, + MatchAnyKeyword, + MatchAllKeyword, + FilterData, + CallbackContext + ); +#endif + + return; +} + +#endif +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION +//+ +// Provider Valve-Main Event Count 14 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_MAIN = {0x3fa9201e, 0x73b0, 0x43fe, {0x98, 0x21, 0x7e, 0x14, 0x53, 0x59, 0xbc, 0x6f}}; + +// +// Opcodes +// +#define _BeginOpcode 0xa +#define _EndOpcode 0xb +#define _StepOpcode 0xc +#define _MarkOpcode 0xd +#define _MarkOpcode1F 0xe +#define _MarkOpcode2F 0xf +#define _MarkOpcode3F 0x10 +#define _MarkOpcode4F 0x11 +#define _MarkOpcode1I 0x12 +#define _MarkOpcode2I 0x13 +#define _MarkOpcode3I 0x14 +#define _MarkOpcode4I 0x15 +#define _MarkOpcode1S 0x16 +#define _MarkOpcode2S 0x17 +#define _InformationOpcode 0x18 + +// +// Tasks +// +#define Block_Task 0x1 +EXTERN_C __declspec(selectany) const GUID BlockId = {0xf15f363a, 0x49fd, 0x4de3, {0x96, 0x7c, 0x17, 0x32, 0x46, 0x49, 0x45, 0xff}}; +#define ThreadID_Task 0x2 +EXTERN_C __declspec(selectany) const GUID ThreadIDId = {0xf15f363a, 0x493d, 0x4dea, {0x96, 0x7c, 0x11, 0x23, 0x46, 0x49, 0x45, 0xff}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Start = {0x64, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define Start_value 0x64 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Stop = {0x65, 0x0, 0x0, 0x0, 0xb, 0x1, 0x0}; +#define Stop_value 0x65 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark = {0x66, 0x0, 0x0, 0x0, 0xd, 0x1, 0x0}; +#define Mark_value 0x66 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark1F = {0x67, 0x0, 0x0, 0x0, 0xe, 0x1, 0x0}; +#define Mark1F_value 0x67 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark2F = {0x68, 0x0, 0x0, 0x0, 0xf, 0x1, 0x0}; +#define Mark2F_value 0x68 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark3F = {0x69, 0x0, 0x0, 0x0, 0x10, 0x1, 0x0}; +#define Mark3F_value 0x69 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark4F = {0x6a, 0x0, 0x0, 0x0, 0x11, 0x1, 0x0}; +#define Mark4F_value 0x6a +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark1I = {0x6b, 0x0, 0x0, 0x0, 0x12, 0x1, 0x0}; +#define Mark1I_value 0x6b +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark2I = {0x6c, 0x0, 0x0, 0x0, 0x13, 0x1, 0x0}; +#define Mark2I_value 0x6c +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark3I = {0x6d, 0x0, 0x0, 0x0, 0x14, 0x1, 0x0}; +#define Mark3I_value 0x6d +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark4I = {0x6e, 0x0, 0x0, 0x0, 0x15, 0x1, 0x0}; +#define Mark4I_value 0x6e +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark1S = {0x6f, 0x0, 0x0, 0x0, 0x16, 0x1, 0x0}; +#define Mark1S_value 0x6f +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mark2S = {0x70, 0x0, 0x0, 0x0, 0x17, 0x1, 0x0}; +#define Mark2S_value 0x70 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Thread_ID = {0x71, 0x0, 0x0, 0x4, 0x18, 0x2, 0x0}; +#define Thread_ID_value 0x71 + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_MainHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_MAIN_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_Main +#define EventRegisterValve_Main() McGenEventRegister(&VALVE_MAIN, McGenControlCallbackV2, &VALVE_MAIN_Context, &Valve_MainHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_Main +#define EventUnregisterValve_Main() McGenEventUnregister(&Valve_MainHandle) +#endif + +// +// Event Macro for Start +// +#define EventWriteStart(Description, Depth)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Start) ?\ + Template_sd(Valve_MainHandle, &Start, Description, Depth)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Stop +// +#define EventWriteStop(Description, Depth, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Stop) ?\ + Template_sdf(Valve_MainHandle, &Stop, Description, Depth, Duration__ms_)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark +// +#define EventWriteMark(Description)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark) ?\ + Template_s(Valve_MainHandle, &Mark, Description)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark1F +// +#define EventWriteMark1F(Description, Data_1)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark1F) ?\ + Template_sf(Valve_MainHandle, &Mark1F, Description, Data_1)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark2F +// +#define EventWriteMark2F(Description, Data_1, Data_2)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark2F) ?\ + Template_sff(Valve_MainHandle, &Mark2F, Description, Data_1, Data_2)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark3F +// +#define EventWriteMark3F(Description, Data_1, Data_2, Data_3)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark3F) ?\ + Template_sfff(Valve_MainHandle, &Mark3F, Description, Data_1, Data_2, Data_3)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark4F +// +#define EventWriteMark4F(Description, Data_1, Data_2, Data_3, Data_4)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark4F) ?\ + Template_sffff(Valve_MainHandle, &Mark4F, Description, Data_1, Data_2, Data_3, Data_4)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark1I +// +#define EventWriteMark1I(Description, Data_1)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark1I) ?\ + Template_sd(Valve_MainHandle, &Mark1I, Description, Data_1)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark2I +// +#define EventWriteMark2I(Description, Data_1, Data_2)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark2I) ?\ + Template_sdd(Valve_MainHandle, &Mark2I, Description, Data_1, Data_2)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark3I +// +#define EventWriteMark3I(Description, Data_1, Data_2, Data_3)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark3I) ?\ + Template_sddd(Valve_MainHandle, &Mark3I, Description, Data_1, Data_2, Data_3)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark4I +// +#define EventWriteMark4I(Description, Data_1, Data_2, Data_3, Data_4)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark4I) ?\ + Template_sdddd(Valve_MainHandle, &Mark4I, Description, Data_1, Data_2, Data_3, Data_4)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark1S +// +#define EventWriteMark1S(Description, Data_1)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark1S) ?\ + Template_ss(Valve_MainHandle, &Mark1S, Description, Data_1)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mark2S +// +#define EventWriteMark2S(Description, Data_1, Data_2)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Mark2S) ?\ + Template_sss(Valve_MainHandle, &Mark2S, Description, Data_1, Data_2)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Thread_ID +// +#define EventWriteThread_ID(ThreadID, ThreadName)\ + MCGEN_ENABLE_CHECK(VALVE_MAIN_Context, Thread_ID) ?\ + Template_ds(Valve_MainHandle, &Thread_ID, ThreadID, ThreadName)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +//+ +// Provider Valve-FrameRate Event Count 2 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_FRAMERATE = {0x47a9201e, 0x73b0, 0x42ce, {0x98, 0x21, 0x7e, 0x13, 0x43, 0x61, 0xbc, 0x6f}}; + +// +// Opcodes +// +#define _RenderFrameMarkOpcode 0xa +#define _SimFrameMarkOpcode 0xb + +// +// Tasks +// +#define Frame_Task 0x1 +EXTERN_C __declspec(selectany) const GUID FrameId = {0xf15f363a, 0x49fd, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x45, 0xff}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR RenderFrameMark = {0xc8, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define RenderFrameMark_value 0xc8 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR SimFrameMark = {0xc9, 0x0, 0x0, 0x0, 0xb, 0x1, 0x0}; +#define SimFrameMark_value 0xc9 + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_FrameRateHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_FRAMERATE_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_FrameRate +#define EventRegisterValve_FrameRate() McGenEventRegister(&VALVE_FRAMERATE, McGenControlCallbackV2, &VALVE_FRAMERATE_Context, &Valve_FrameRateHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_FrameRate +#define EventUnregisterValve_FrameRate() McGenEventUnregister(&Valve_FrameRateHandle) +#endif + +// +// Event Macro for RenderFrameMark +// +#define EventWriteRenderFrameMark(Frame_number, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_FRAMERATE_Context, RenderFrameMark) ?\ + Template_df(Valve_FrameRateHandle, &RenderFrameMark, Frame_number, Duration__ms_)\ + : ERROR_SUCCESS\ + +// +// Event Macro for SimFrameMark +// +#define EventWriteSimFrameMark(Frame_number, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_FRAMERATE_Context, SimFrameMark) ?\ + Template_df(Valve_FrameRateHandle, &SimFrameMark, Frame_number, Duration__ms_)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +//+ +// Provider Valve-ServerFrameRate Event Count 2 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_SERVERFRAMERATE = {0x58a9201e, 0x73b0, 0x42ce, {0x98, 0x21, 0x7e, 0x13, 0x43, 0x61, 0xbc, 0x70}}; + +// +// Opcodes +// +#define _ServerRenderFrameMarkOpcode 0xa +#define _ServerSimFrameMarkOpcode 0xb + +// +// Tasks +// +#define Frame_Task 0x1 +EXTERN_C __declspec(selectany) const GUID ServerFrameId = {0x025f363a, 0x49fd, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x45, 0x00}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR ServerRenderFrameMark = {0x12c, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define ServerRenderFrameMark_value 0x12c +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR ServerSimFrameMark = {0x12d, 0x0, 0x0, 0x0, 0xb, 0x1, 0x0}; +#define ServerSimFrameMark_value 0x12d + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_ServerFrameRateHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_SERVERFRAMERATE_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_ServerFrameRate +#define EventRegisterValve_ServerFrameRate() McGenEventRegister(&VALVE_SERVERFRAMERATE, McGenControlCallbackV2, &VALVE_SERVERFRAMERATE_Context, &Valve_ServerFrameRateHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_ServerFrameRate +#define EventUnregisterValve_ServerFrameRate() McGenEventUnregister(&Valve_ServerFrameRateHandle) +#endif + +// +// Event Macro for ServerRenderFrameMark +// +#define EventWriteServerRenderFrameMark(Frame_number, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_SERVERFRAMERATE_Context, ServerRenderFrameMark) ?\ + Template_df(Valve_ServerFrameRateHandle, &ServerRenderFrameMark, Frame_number, Duration__ms_)\ + : ERROR_SUCCESS\ + +// +// Event Macro for ServerSimFrameMark +// +#define EventWriteServerSimFrameMark(Frame_number, Duration__ms_)\ + MCGEN_ENABLE_CHECK(VALVE_SERVERFRAMERATE_Context, ServerSimFrameMark) ?\ + Template_df(Valve_ServerFrameRateHandle, &ServerSimFrameMark, Frame_number, Duration__ms_)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +//+ +// Provider Valve-Input Event Count 5 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_INPUT = {0x1432afee, 0x73b0, 0x42ce, {0x98, 0x21, 0x7e, 0x13, 0x43, 0x61, 0xb4, 0x33}}; + +// +// Opcodes +// +#define _MouseDownOpcode 0xa +#define _MouseUpOpcode 0xb +#define _KeyDownOpcode 0xc +#define _MouseMoveOpcode 0xd +#define _MouseWheelOpcode 0xe + +// +// Tasks +// +#define Mouse_Task 0x1 +EXTERN_C __declspec(selectany) const GUID MouseId = {0x363a49fd, 0xf15f, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x44, 0x33}}; +#define Keyboard_Task 0x2 +EXTERN_C __declspec(selectany) const GUID KeyboardId = {0x123a49fd, 0xf15f, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0xbe, 0xad}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mouse_down = {0x190, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define Mouse_down_value 0x190 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mouse_up = {0x191, 0x0, 0x0, 0x0, 0xb, 0x1, 0x0}; +#define Mouse_up_value 0x191 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Key_down = {0x192, 0x0, 0x0, 0x0, 0xc, 0x2, 0x0}; +#define Key_down_value 0x192 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mouse_Move = {0x193, 0x0, 0x0, 0x0, 0xd, 0x1, 0x0}; +#define Mouse_Move_value 0x193 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Mouse_Wheel = {0x194, 0x0, 0x0, 0x0, 0xe, 0x1, 0x0}; +#define Mouse_Wheel_value 0x194 + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_InputHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_INPUT_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_Input +#define EventRegisterValve_Input() McGenEventRegister(&VALVE_INPUT, McGenControlCallbackV2, &VALVE_INPUT_Context, &Valve_InputHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_Input +#define EventUnregisterValve_Input() McGenEventUnregister(&Valve_InputHandle) +#endif + +// +// Event Macro for Mouse_down +// +#define EventWriteMouse_down(x, y, Button_Type)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Mouse_down) ?\ + Template_ddd(Valve_InputHandle, &Mouse_down, x, y, Button_Type)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mouse_up +// +#define EventWriteMouse_up(x, y, Button_Type)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Mouse_up) ?\ + Template_ddd(Valve_InputHandle, &Mouse_up, x, y, Button_Type)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Key_down +// +#define EventWriteKey_down(Character, Scan_Code, Virtual_Code)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Key_down) ?\ + Template_sdd(Valve_InputHandle, &Key_down, Character, Scan_Code, Virtual_Code)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mouse_Move +// +#define EventWriteMouse_Move(x, y)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Mouse_Move) ?\ + Template_dd(Valve_InputHandle, &Mouse_Move, x, y)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Mouse_Wheel +// +#define EventWriteMouse_Wheel(x, y, wheelDelta)\ + MCGEN_ENABLE_CHECK(VALVE_INPUT_Context, Mouse_Wheel) ?\ + Template_ddd(Valve_InputHandle, &Mouse_Wheel, x, y, wheelDelta)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +//+ +// Provider Valve-Network Event Count 3 +//+ +EXTERN_C __declspec(selectany) const GUID VALVE_NETWORK = {0x4372afee, 0x73b0, 0x42ce, {0x98, 0x21, 0x7e, 0x13, 0x43, 0x61, 0xb5, 0x19}}; + +// +// Opcodes +// +#define _SendOpcode 0xa +#define _ThrottledOpcode 0xb +#define _ReadOpcode 0xc + +// +// Tasks +// +#define Transmit_Task 0x1 +EXTERN_C __declspec(selectany) const GUID TransmitId = {0x932a49fd, 0xf15f, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x49, 0x01}}; +#define Network_Task 0x2 +EXTERN_C __declspec(selectany) const GUID ThrottledId = {0xa32a49fd, 0xf15f, 0x4ffa, {0x96, 0x7c, 0x17, 0x39, 0x36, 0x49, 0x49, 0x02}}; + +// +// Event Descriptors +// +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR SendPacket = {0x1f4, 0x0, 0x0, 0x0, 0xa, 0x1, 0x0}; +#define SendPacket_value 0x1f4 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR Throttled = {0x1f5, 0x0, 0x0, 0x0, 0xb, 0x2, 0x0}; +#define Throttled_value 0x1f5 +EXTERN_C __declspec(selectany) const EVENT_DESCRIPTOR ReadPacket = {0x1f6, 0x0, 0x0, 0x0, 0xc, 0x1, 0x0}; +#define ReadPacket_value 0x1f6 + +// +// Note on Generate Code from Manifest Windows Vista and above +// +//Structures : are handled as a size and pointer pairs. The macro for the event will have an extra +//parameter for the size in bytes of the structure. Make sure that your structures have no extra padding. +// +//Strings: There are several cases that can be described in the manifest. For array of variable length +//strings, the generated code will take the count of characters for the whole array as an input parameter. +// +//SID No support for array of SIDs, the macro will take a pointer to the SID and use appropriate +//GetLengthSid function to get the length. +// + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Globals +// + +EXTERN_C __declspec(selectany) REGHANDLE Valve_NetworkHandle = (REGHANDLE)0; + +EXTERN_C __declspec(selectany) MCGEN_TRACE_CONTEXT VALVE_NETWORK_Context = {0}; + +#if !defined(McGenEventRegisterUnregister) +#define McGenEventRegisterUnregister +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventRegister( + __in LPCGUID ProviderId, + __in_opt PENABLECALLBACK EnableCallback, + __in_opt PVOID CallbackContext, + __inout PREGHANDLE RegHandle + ) +/*++ + +Routine Description: + + This function register the provider with ETW USER mode. + +Arguments: + ProviderId - Provider Id to be register with ETW. + + EnableCallback - Callback to be used. + + CallbackContext - Context for this provider. + + RegHandle - Pointer to Registration handle. + +Remarks: + + If the handle != NULL will return ERROR_SUCCESS + +--*/ +{ + ULONG Error; + + + if (*RegHandle) { + // + // already registered + // + return ERROR_SUCCESS; + } + + Error = EventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle); + + return Error; +} + + +DECLSPEC_NOINLINE __inline +ULONG __stdcall +McGenEventUnregister(__inout PREGHANDLE RegHandle) +/*++ + +Routine Description: + + Unregister from ETW USER mode + +Arguments: + RegHandle this is the pointer to the provider context +Remarks: + If Provider has not register RegHandle = NULL, + return ERROR_SUCCESS +--*/ +{ + ULONG Error; + + + if(!(*RegHandle)) { + // + // Provider has not registerd + // + return ERROR_SUCCESS; + } + + Error = EventUnregister(*RegHandle); + *RegHandle = (REGHANDLE)0; + + return Error; +} +#endif +// +// Register with ETW Vista + +// +#ifndef EventRegisterValve_Network +#define EventRegisterValve_Network() McGenEventRegister(&VALVE_NETWORK, McGenControlCallbackV2, &VALVE_NETWORK_Context, &Valve_NetworkHandle) +#endif + +// +// UnRegister with ETW +// +#ifndef EventUnregisterValve_Network +#define EventUnregisterValve_Network() McGenEventUnregister(&Valve_NetworkHandle) +#endif + +// +// Event Macro for SendPacket +// +#define EventWriteSendPacket(To, WireSize, outSequenceNR, outSequenceNrAck, CumulativeWireSize)\ + MCGEN_ENABLE_CHECK(VALVE_NETWORK_Context, SendPacket) ?\ + Template_sdddd(Valve_NetworkHandle, &SendPacket, To, WireSize, outSequenceNR, outSequenceNrAck, CumulativeWireSize)\ + : ERROR_SUCCESS\ + +// +// Event Macro for Throttled +// +#define EventWriteThrottled()\ + MCGEN_ENABLE_CHECK(VALVE_NETWORK_Context, Throttled) ?\ + TemplateEventDescriptor(Valve_NetworkHandle, &Throttled)\ + : ERROR_SUCCESS\ + +// +// Event Macro for ReadPacket +// +#define EventWriteReadPacket(From, WireSize, inSequenceNR, outSequenceNrAck, CumulativeWireSize)\ + MCGEN_ENABLE_CHECK(VALVE_NETWORK_Context, ReadPacket) ?\ + Template_sdddd(Valve_NetworkHandle, &ReadPacket, From, WireSize, inSequenceNR, outSequenceNrAck, CumulativeWireSize)\ + : ERROR_SUCCESS\ + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + + +// +// Allow Diasabling of code generation +// +#ifndef MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +// +// Template Functions +// +// +//Template from manifest : T_Start +// +#ifndef Template_sd_def +#define Template_sd_def +ETW_INLINE +ULONG +Template_sd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Depth + ) +{ +#define ARGUMENT_COUNT_sd 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sd]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Depth, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sd, EventData); +} +#endif + +// +//Template from manifest : T_End +// +#ifndef Template_sdf_def +#define Template_sdf_def +ETW_INLINE +ULONG +Template_sdf( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Depth, + __in const float Duration__ms_ + ) +{ +#define ARGUMENT_COUNT_sdf 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sdf]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Depth, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Duration__ms_, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sdf, EventData); +} +#endif + +// +//Template from manifest : T_Mark +// +#ifndef Template_s_def +#define Template_s_def +ETW_INLINE +ULONG +Template_s( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description + ) +{ +#define ARGUMENT_COUNT_s 1 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_s]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_s, EventData); +} +#endif + +// +//Template from manifest : T_Mark1F +// +#ifndef Template_sf_def +#define Template_sf_def +ETW_INLINE +ULONG +Template_sf( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const float Data_1 + ) +{ +#define ARGUMENT_COUNT_sf 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sf]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sf, EventData); +} +#endif + +// +//Template from manifest : T_Mark2F +// +#ifndef Template_sff_def +#define Template_sff_def +ETW_INLINE +ULONG +Template_sff( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const float Data_1, + __in const float Data_2 + ) +{ +#define ARGUMENT_COUNT_sff 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sff]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const float) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sff, EventData); +} +#endif + +// +//Template from manifest : T_Mark3F +// +#ifndef Template_sfff_def +#define Template_sfff_def +ETW_INLINE +ULONG +Template_sfff( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const float Data_1, + __in const float Data_2, + __in const float Data_3 + ) +{ +#define ARGUMENT_COUNT_sfff 4 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sfff]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const float) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const float) ); + + EventDataDescCreate(&EventData[3], &Data_3, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sfff, EventData); +} +#endif + +// +//Template from manifest : T_Mark4F +// +#ifndef Template_sffff_def +#define Template_sffff_def +ETW_INLINE +ULONG +Template_sffff( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const float Data_1, + __in const float Data_2, + __in const float Data_3, + __in const float Data_4 + ) +{ +#define ARGUMENT_COUNT_sffff 5 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sffff]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const float) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const float) ); + + EventDataDescCreate(&EventData[3], &Data_3, sizeof(const float) ); + + EventDataDescCreate(&EventData[4], &Data_4, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sffff, EventData); +} +#endif + +// +//Template from manifest : T_Mark2I +// +#ifndef Template_sdd_def +#define Template_sdd_def +ETW_INLINE +ULONG +Template_sdd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Data_1, + __in const signed int Data_2 + ) +{ +#define ARGUMENT_COUNT_sdd 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sdd]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sdd, EventData); +} +#endif + +// +//Template from manifest : T_Mark3I +// +#ifndef Template_sddd_def +#define Template_sddd_def +ETW_INLINE +ULONG +Template_sddd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Data_1, + __in const signed int Data_2, + __in const signed int Data_3 + ) +{ +#define ARGUMENT_COUNT_sddd 4 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sddd]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[3], &Data_3, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sddd, EventData); +} +#endif + +// +//Template from manifest : T_Mark4I +// +#ifndef Template_sdddd_def +#define Template_sdddd_def +ETW_INLINE +ULONG +Template_sdddd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in const signed int Data_1, + __in const signed int Data_2, + __in const signed int Data_3, + __in const signed int Data_4 + ) +{ +#define ARGUMENT_COUNT_sdddd 5 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sdddd]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], &Data_1, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Data_2, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[3], &Data_3, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[4], &Data_4, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sdddd, EventData); +} +#endif + +// +//Template from manifest : T_Mark1S +// +#ifndef Template_ss_def +#define Template_ss_def +ETW_INLINE +ULONG +Template_ss( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in_opt LPCSTR Data_1 + ) +{ +#define ARGUMENT_COUNT_ss 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_ss]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], + (Data_1 != NULL) ? Data_1 : "NULL", + (Data_1 != NULL) ? (ULONG)((strlen(Data_1) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_ss, EventData); +} +#endif + +// +//Template from manifest : T_Mark2S +// +#ifndef Template_sss_def +#define Template_sss_def +ETW_INLINE +ULONG +Template_sss( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in_opt LPCSTR Description, + __in_opt LPCSTR Data_1, + __in_opt LPCSTR Data_2 + ) +{ +#define ARGUMENT_COUNT_sss 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_sss]; + + EventDataDescCreate(&EventData[0], + (Description != NULL) ? Description : "NULL", + (Description != NULL) ? (ULONG)((strlen(Description) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[1], + (Data_1 != NULL) ? Data_1 : "NULL", + (Data_1 != NULL) ? (ULONG)((strlen(Data_1) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + EventDataDescCreate(&EventData[2], + (Data_2 != NULL) ? Data_2 : "NULL", + (Data_2 != NULL) ? (ULONG)((strlen(Data_2) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_sss, EventData); +} +#endif + +// +//Template from manifest : T_ThreadID +// +#ifndef Template_ds_def +#define Template_ds_def +ETW_INLINE +ULONG +Template_ds( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in const signed int ThreadID, + __in_opt LPCSTR ThreadName + ) +{ +#define ARGUMENT_COUNT_ds 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_ds]; + + EventDataDescCreate(&EventData[0], &ThreadID, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[1], + (ThreadName != NULL) ? ThreadName : "NULL", + (ThreadName != NULL) ? (ULONG)((strlen(ThreadName) + 1) * sizeof(CHAR)) : (ULONG)sizeof("NULL")); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_ds, EventData); +} +#endif + +// +//Template from manifest : T_FrameMark +// +#ifndef Template_df_def +#define Template_df_def +ETW_INLINE +ULONG +Template_df( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in const signed int Frame_number, + __in const float Duration__ms_ + ) +{ +#define ARGUMENT_COUNT_df 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_df]; + + EventDataDescCreate(&EventData[0], &Frame_number, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[1], &Duration__ms_, sizeof(const float) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_df, EventData); +} +#endif + +// +//Template from manifest : T_MouseClick +// +#ifndef Template_ddd_def +#define Template_ddd_def +ETW_INLINE +ULONG +Template_ddd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in const signed int x, + __in const signed int y, + __in const signed int Button_Type + ) +{ +#define ARGUMENT_COUNT_ddd 3 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_ddd]; + + EventDataDescCreate(&EventData[0], &x, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[1], &y, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[2], &Button_Type, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_ddd, EventData); +} +#endif + +// +//Template from manifest : T_MouseMove +// +#ifndef Template_dd_def +#define Template_dd_def +ETW_INLINE +ULONG +Template_dd( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor, + __in const signed int x, + __in const signed int y + ) +{ +#define ARGUMENT_COUNT_dd 2 + + EVENT_DATA_DESCRIPTOR EventData[ARGUMENT_COUNT_dd]; + + EventDataDescCreate(&EventData[0], &x, sizeof(const signed int) ); + + EventDataDescCreate(&EventData[1], &y, sizeof(const signed int) ); + + return EventWrite(RegHandle, Descriptor, ARGUMENT_COUNT_dd, EventData); +} +#endif + +// +//Template from manifest : T_Throttled +// +#ifndef TemplateEventDescriptor_def +#define TemplateEventDescriptor_def + + +ETW_INLINE +ULONG +TemplateEventDescriptor( + __in REGHANDLE RegHandle, + __in PCEVENT_DESCRIPTOR Descriptor + ) +{ + return EventWrite(RegHandle, Descriptor, 0, NULL); +} +#endif + +#endif // MCGEN_DISABLE_PROVIDER_CODE_GENERATION + +#if defined(__cplusplus) +}; +#endif + diff --git a/tier0/ValveETWProviderEvents.rc b/tier0/ValveETWProviderEvents.rc new file mode 100644 index 0000000..aaa8f50 --- /dev/null +++ b/tier0/ValveETWProviderEvents.rc @@ -0,0 +1,3 @@ +LANGUAGE 0x9,0x1 +1 11 "ValveETWProviderEvents_MSG00001.bin" +1 WEVT_TEMPLATE "ValveETWProviderEventsTEMP.BIN" diff --git a/tier0/ValveETWProviderEventsTEMP.BIN b/tier0/ValveETWProviderEventsTEMP.BIN new file mode 100644 index 0000000000000000000000000000000000000000..95c748d094b2be28934d8049fa37f3608e79c6c8 GIT binary patch literal 12762 zcmeHN3v8Cv89ptAmbOS~3)Cs~N2(Ttjb4PxMGECdDh2*Qg(98U(n6~(Exl+_VhAIS z5e=2gXdEaqT)GHSC(DdBMhOdbaf56wB0GoR;B2+<*si{qqCKAjmcNX~vlJ zt7~5P$CT6i_TT$ea>}Z=L!KTQ5+Xvnx}L|>?2UtkUpy*`#Tro~W{U!R7x3|!U0pNL zZ>b2=pko0UG;e2^SOBDQJS4=xkSHJ&i)yh_REbhi2?C{}0^~x6fJ_+Z+~uG%%8kxI zNr%su;Moun9!6fUh4K(a`_veRlc8qAVQKSR-L&85QokTf{c_~@QD;@nWmQLcDbI870sd#fc3^yRzo%1su*XXbC}=vb6AXW- z!^Q*FSN>aNk}B7sOgbP0>OMd95lSY@cHfrxlReY2kJaBd_x937ZH7#42I_S!lm1CG z4H6yD?O}D>X$3#oblF{UfyE4V;w1rgC1h-sdzxH`a(@U;i>A#_OQe$fxHW$%J~GgM zc4vHSc>J2WE<^4Q{9o>7PNe%xQ||rKnsjJQShv<#bD{A@?A7&Vt_v*R1y<<-+XUFv z(j}{YrFZL>9v91^m43Z)?-$3WAN=fhkH5IQz5MRZkFJ%tXlAUsZSTOD*!0|~39!EM z*&Y{9BD-c>+#lC@sV$-D$OlXB*jM)2tGln2xERixK|eAO6&FoTTkRp9mmoBq*Arb} zvjOWX|E>D#9v2&jDw(`czdUW^gSC^Fe0a|nQwjoU*GgRcrZVZDM3sLuC92y_2l#21 zXFM-+Xu6D(U0~AyyAm?C%H2IKwhmKrpL5e&7tX)g_|CLv%I-PTx#Ik_5*NQ)x%W?N zR9rN*#_9)V$2xW7As1MU3v9CsY&T$6OP8$rLp};7n{ftOc;X^4T62+rd!A#T{EqZ(3O%K4|pzzl|NW>-ErUB`(T} zS-<0=$!RNJ$D=;Trt^BP3oIY7zVhFyzo~K^$|M~^FzX$2QSYQo-neVygPrj&@7a1` z&!s&l(tc{lWFMw_x-U+}ytPg-1`tP`B0}V%a#)30WPvCW*{CeA0x5ml3#i%$IqdLsS|K~$WRpJr( zKXu6bKSj)Oz*1dcX)dsI7g!23flXf{)diO30!w#+rQjq^dJ0DQX5-AOQ2(w%RW(>; z6(YyKBqtN|v;(7#64(vCCFB3cB+sUJJX;{jfRj6O@jUjlDbnD3g(;I({QYwFC-Z-{ zeq`#LY=2h--Wzb7@>VaQE{GnF-11NBu^T019evB1A6ScJt5B!ITKg^1sfjUi?v^OE_{E=1=YsGKC zT|CAEpL!tTtoTP^a5sD4mlHqUik}BzCVSx1e#LMregMjo?tx!H{1I0CCge<+9{AD= zjI`pPM(oe=z+VgUVF}c1)u<1Zh*f#OuZOOkv*{b(y`2F%@mMN_s6-x&lN6fx?g=dc z@A9~VB=oE^=cA`xmZ0x# zr+UDASkh}>n3D57z4u`(q>Yk~La12yzFbSi!VZj+FtZ3JWrX2*gX@`*M6I(JzO~$S zR{3PP1it8BGtg6J{7`q4fBpAD_}9B9!H4ERH|RryYYCzIO8QCaw%Rx9r5M}&WD)eR z+2Gqmo36b}e4C#nev1b_^*jd3tLu3t-r;cl1|Nw*&qu5CB%!CCm!PMf*P-ui>q*Zi z`mTRH&%+tnbUm;0Dg6rSg4O+5*so-5yx$wcA4u^mV_F*f=Zb z>umz@ZT3L?CZEExzThGccdr_s|MI?&S|BF1vIca8Q?gM5L{ z(eIaFpQOc4{uo6+@_fUow&O3Jz4O?w6Z1l!+iv*%-vgy5zfa$6_4~Z1IsATzs0(`d zUO%%A&+vB@sJP5@ti$s=)`>z+UyIDWM*gD1hiq{ja`ZCzw@jF1@cA4_^~C4tn78uz zHb5NbA1tzPr{qng068;nFeLn||4l)kdK7B&%NQ_cyT#+A^EYuK@OKhvflg*IS;qNYl-H_!|5xp8Rs^ z*{y-87o$FIuiO^tD>Kg9f~oKPyzl|Nu8KSc8z{zK8KN1#`4chS1L! z&QpOEdEZg>0TX_|tW*#-4_NCoJnQqM#JH?{SCH<~ZjNC}-u>CLx!vP0XEW0@Hoefxfqm`N0V=na|TVybczS z=lokh%H%oms;uEmJ=vz7Q>fV@?C4D4o}gI1aw ztr$;QRbr;BVDa}fGMicGpg7r$Vl1K@Z^*UqVX8Jq+jX8fOv`Qbn7W(EHJa|*8eL#- zyTDpqU=iS9H0V)QZ1gSm-4O1>$D6N8-M!{bUXc#B&>IdAmwFrEz-XJWSL*Gb+SaZ9 z<;gdbo}ac{Ozybv`-a|r4^y6cYv#$#njK_9lS%KH3%jPjcKoHOpa1C8eaFX)ong@1 z{55t!jj`GRerxU2$1r(UuYk<2pe^(|XJaM&da(rE4))Uvq3L#T*ah~U3+$W=EFQi# z2K0EBq^#I_vIQ@`@;SQxD{TvAJ&OL^%sncxXEXQMAjc~AXfVz~xvRGy>MW|xUIE`& z1MgETBQ(#<8W!JIvGE;&9N>$2LJfLGPuhvf1;%X;<^tIv9axL=#@sKoxzHHIEAlWG zJmuY^8hkCq+H4BWcV*W7=(Q~3F<;n=UY#3pm>aR>3+oX-bL9E)TV10EmTVV^AH^N2 e#(k#4kK`G6r)~KEB7QW#R3kq9PLT(FA^r +#elif defined( POSIX ) +#include +#endif +#include "resource.h" +#include "tier0/valve_on.h" +#include "tier0/threadtools.h" + +#if defined( POSIX ) +#include +#endif + +#if defined( LINUX ) || defined( USE_SDL ) + +// We lazily load the SDL shared object, and only reference functions if it's +// available, so this can be included on the dedicated server too. +#include "SDL.h" + +typedef int ( SDLCALL FUNC_SDL_ShowMessageBox )( const SDL_MessageBoxData *messageboxdata, int *buttonid ); +#endif + +class CDialogInitInfo +{ +public: + const tchar *m_pFilename; + int m_iLine; + const tchar *m_pExpression; +}; + + +class CAssertDisable +{ +public: + tchar m_Filename[512]; + + // If these are not -1, then this CAssertDisable only disables asserts on lines between + // these values (inclusive). + int m_LineMin; + int m_LineMax; + + // Decremented each time we hit this assert and ignore it, until it's 0. + // Then the CAssertDisable is removed. + // If this is -1, then we always ignore this assert. + int m_nIgnoreTimes; + + CAssertDisable *m_pNext; +}; + +#ifdef _WIN32 +static HINSTANCE g_hTier0Instance = 0; +#endif + +static bool g_bAssertsEnabled = true; + +static CAssertDisable *g_pAssertDisables = NULL; + +#if ( defined( _WIN32 ) && !defined( _X360 ) ) +static int g_iLastLineRange = 5; +static int g_nLastIgnoreNumTimes = 1; +#endif +#if defined( _X360 ) +static int g_VXConsoleAssertReturnValue = -1; +#endif + +// Set to true if they want to break in the debugger. +static bool g_bBreak = false; + +static CDialogInitInfo g_Info; + + +// -------------------------------------------------------------------------------- // +// Internal functions. +// -------------------------------------------------------------------------------- // + +#if defined(_WIN32) && !defined(STATIC_TIER0) +extern "C" BOOL APIENTRY MemDbgDllMain( HMODULE hDll, DWORD dwReason, PVOID pvReserved ); + +BOOL WINAPI DllMain( + HINSTANCE hinstDLL, // handle to the DLL module + DWORD fdwReason, // reason for calling function + LPVOID lpvReserved // reserved +) +{ + g_hTier0Instance = hinstDLL; +#ifdef DEBUG + MemDbgDllMain( hinstDLL, fdwReason, lpvReserved ); +#endif + return true; +} +#endif + +static bool IsDebugBreakEnabled() +{ + static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-debugbreak") ) != NULL ) || \ + ( _tcsstr( Plat_GetCommandLine(), _T("-raiseonassert") ) != NULL ) || \ + getenv( "RAISE_ON_ASSERT" ); + return bResult; +} + +static bool AreAssertsDisabled() +{ + static bool bResult = ( _tcsstr( Plat_GetCommandLine(), _T("-noassert") ) != NULL ); + return bResult; +} + +static bool AreAssertsEnabledInFileLine( const tchar *pFilename, int iLine ) +{ + CAssertDisable **pPrev = &g_pAssertDisables; + CAssertDisable *pNext; + for ( CAssertDisable *pCur=g_pAssertDisables; pCur; pCur=pNext ) + { + pNext = pCur->m_pNext; + + if ( _tcsicmp( pFilename, pCur->m_Filename ) == 0 ) + { + // Are asserts disabled in the whole file? + bool bAssertsEnabled = true; + if ( pCur->m_LineMin == -1 && pCur->m_LineMax == -1 ) + bAssertsEnabled = false; + + // Are asserts disabled on the specified line? + if ( iLine >= pCur->m_LineMin && iLine <= pCur->m_LineMax ) + bAssertsEnabled = false; + + if ( !bAssertsEnabled ) + { + // If this assert is only disabled for the next N times, then countdown.. + if ( pCur->m_nIgnoreTimes > 0 ) + { + --pCur->m_nIgnoreTimes; + if ( pCur->m_nIgnoreTimes == 0 ) + { + // Remove this one from the list. + *pPrev = pNext; + delete pCur; + continue; + } + } + + return false; + } + } + + pPrev = &pCur->m_pNext; + } + + return true; +} + + +CAssertDisable* CreateNewAssertDisable( const tchar *pFilename ) +{ + CAssertDisable *pDisable = new CAssertDisable; + pDisable->m_pNext = g_pAssertDisables; + g_pAssertDisables = pDisable; + + pDisable->m_LineMin = pDisable->m_LineMax = -1; + pDisable->m_nIgnoreTimes = -1; + + _tcsncpy( pDisable->m_Filename, g_Info.m_pFilename, sizeof( pDisable->m_Filename ) - 1 ); + pDisable->m_Filename[ sizeof( pDisable->m_Filename ) - 1 ] = 0; + + return pDisable; +} + + +void IgnoreAssertsInCurrentFile() +{ + CreateNewAssertDisable( g_Info.m_pFilename ); +} + + +CAssertDisable* IgnoreAssertsNearby( int nRange ) +{ + CAssertDisable *pDisable = CreateNewAssertDisable( g_Info.m_pFilename ); + pDisable->m_LineMin = g_Info.m_iLine - nRange; + pDisable->m_LineMax = g_Info.m_iLine - nRange; + return pDisable; +} + + +#if ( defined( _WIN32 ) && !defined( _X360 ) ) +INT_PTR CALLBACK AssertDialogProc( + HWND hDlg, // handle to dialog box + UINT uMsg, // message + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter +) +{ + switch( uMsg ) + { + case WM_INITDIALOG: + { +#ifdef TCHAR_IS_WCHAR + SetDlgItemTextW( hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression ); + SetDlgItemTextW( hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename ); +#else + SetDlgItemText( hDlg, IDC_ASSERT_MSG_CTRL, g_Info.m_pExpression ); + SetDlgItemText( hDlg, IDC_FILENAME_CONTROL, g_Info.m_pFilename ); +#endif + SetDlgItemInt( hDlg, IDC_LINE_CONTROL, g_Info.m_iLine, false ); + SetDlgItemInt( hDlg, IDC_IGNORE_NUMLINES, g_iLastLineRange, false ); + SetDlgItemInt( hDlg, IDC_IGNORE_NUMTIMES, g_nLastIgnoreNumTimes, false ); + + // Center the dialog. + RECT rcDlg, rcDesktop; + GetWindowRect( hDlg, &rcDlg ); + GetWindowRect( GetDesktopWindow(), &rcDesktop ); + SetWindowPos( + hDlg, + HWND_TOP, + ((rcDesktop.right-rcDesktop.left) - (rcDlg.right-rcDlg.left)) / 2, + ((rcDesktop.bottom-rcDesktop.top) - (rcDlg.bottom-rcDlg.top)) / 2, + 0, + 0, + SWP_NOSIZE ); + } + return true; + + case WM_COMMAND: + { + switch( LOWORD( wParam ) ) + { + case IDC_IGNORE_FILE: + { + IgnoreAssertsInCurrentFile(); + EndDialog( hDlg, 0 ); + return true; + } + + // Ignore this assert N times. + case IDC_IGNORE_THIS: + { + BOOL bTranslated = false; + UINT value = GetDlgItemInt( hDlg, IDC_IGNORE_NUMTIMES, &bTranslated, false ); + if ( bTranslated && value > 1 ) + { + CAssertDisable *pDisable = IgnoreAssertsNearby( 0 ); + pDisable->m_nIgnoreTimes = value - 1; + g_nLastIgnoreNumTimes = value; + } + + EndDialog( hDlg, 0 ); + return true; + } + + // Always ignore this assert. + case IDC_IGNORE_ALWAYS: + { + IgnoreAssertsNearby( 0 ); + EndDialog( hDlg, 0 ); + return true; + } + + case IDC_IGNORE_NEARBY: + { + BOOL bTranslated = false; + UINT value = GetDlgItemInt( hDlg, IDC_IGNORE_NUMLINES, &bTranslated, false ); + if ( !bTranslated || value < 1 ) + return true; + + IgnoreAssertsNearby( value ); + EndDialog( hDlg, 0 ); + return true; + } + + case IDC_IGNORE_ALL: + { + g_bAssertsEnabled = false; + EndDialog( hDlg, 0 ); + return true; + } + + case IDC_BREAK: + { + g_bBreak = true; + EndDialog( hDlg, 0 ); + return true; + } + } + + case WM_KEYDOWN: + { + // Escape? + if ( wParam == 2 ) + { + // Ignore this assert. + EndDialog( hDlg, 0 ); + return true; + } + } + + } + return true; + } + + return FALSE; +} + + +static HWND g_hBestParentWindow; + + +static BOOL CALLBACK ParentWindowEnumProc( + HWND hWnd, // handle to parent window + LPARAM lParam // application-defined value +) +{ + if ( IsWindowVisible( hWnd ) ) + { + DWORD procID; + GetWindowThreadProcessId( hWnd, &procID ); + if ( procID == (DWORD)lParam ) + { + g_hBestParentWindow = hWnd; + return FALSE; // don't iterate any more. + } + } + return TRUE; +} + + +static HWND FindLikelyParentWindow() +{ + // Enumerate top-level windows and take the first visible one with our processID. + g_hBestParentWindow = NULL; + EnumWindows( ParentWindowEnumProc, GetCurrentProcessId() ); + return g_hBestParentWindow; +} +#endif // ( defined( _WIN32 ) && !defined( _X360 ) ) + +// -------------------------------------------------------------------------------- // +// Interface functions. +// -------------------------------------------------------------------------------- // + +// provides access to the global that turns asserts on and off +DBG_INTERFACE bool AreAllAssertsDisabled() +{ + return !g_bAssertsEnabled; +} + +DBG_INTERFACE void SetAllAssertsDisabled( bool bAssertsDisabled ) +{ + g_bAssertsEnabled = !bAssertsDisabled; +} + +#if defined( LINUX ) || defined( USE_SDL ) +SDL_Window *g_SDLWindow = NULL; + +DBG_INTERFACE void SetAssertDialogParent( struct SDL_Window *window ) +{ + g_SDLWindow = window; +} + +DBG_INTERFACE struct SDL_Window * GetAssertDialogParent() +{ + return g_SDLWindow; +} +#endif + +DBG_INTERFACE bool ShouldUseNewAssertDialog() +{ + static bool bMPIWorker = ( _tcsstr( Plat_GetCommandLine(), _T("-mpi_worker") ) != NULL ); + if ( bMPIWorker ) + { + return false; + } + +#ifdef DBGFLAG_ASSERTDLG + return true; // always show an assert dialog +#else + return Plat_IsInDebugSession(); // only show an assert dialog if the process is being debugged +#endif // DBGFLAG_ASSERTDLG +} + +#if defined( POSIX ) + +#include + +static void SpewBacktrace() +{ + void *buffer[ 16 ]; + int nptrs = backtrace( buffer, ARRAYSIZE( buffer ) ); + if ( nptrs ) + { + char **strings = backtrace_symbols(buffer, nptrs); + if ( strings ) + { + for ( int i = 0; i < nptrs; i++) + { + const char *module = strrchr( strings[ i ], '/' ); + module = module ? ( module + 1 ) : strings[ i ]; + + printf(" %s\n", module ); + } + + free( strings ); + } + } +} + +#endif + +DBG_INTERFACE bool DoNewAssertDialog( const tchar *pFilename, int line, const tchar *pExpression ) +{ + LOCAL_THREAD_LOCK(); + + if ( AreAssertsDisabled() ) + return false; + + // Have ALL Asserts been disabled? + if ( !g_bAssertsEnabled ) + return false; + + // Has this specific Assert been disabled? + if ( !AreAssertsEnabledInFileLine( pFilename, line ) ) + return false; + + // Assert not suppressed. Spew it, and optionally a backtrace. +#if defined( POSIX ) + if( isatty( STDERR_FILENO ) ) + { + #define COLOR_YELLOW "\033[1;33m" + #define COLOR_GREEN "\033[1;32m" + #define COLOR_RED "\033[1;31m" + #define COLOR_END "\033[0m" + fprintf(stderr, COLOR_YELLOW "ASSERT:" COLOR_END " " COLOR_RED "%s" COLOR_GREEN ":%i:" COLOR_END " " COLOR_RED "%s" COLOR_END "\n", + pFilename, line, pExpression); + if ( getenv( "POSIX_ASSERT_BACKTRACE" ) ) + { + SpewBacktrace(); + } + } + else +#endif + { + fprintf(stderr, "ASSERT: %s:%i: %s\n", pFilename, line, pExpression); + } + + // If they have the old mode enabled (always break immediately), then just break right into + // the debugger like we used to do. + if ( IsDebugBreakEnabled() ) + return true; + + // Now create the dialog. Just return true for old-style debug break upon failure. + g_Info.m_pFilename = pFilename; + g_Info.m_iLine = line; + g_Info.m_pExpression = pExpression; + + g_bBreak = false; + +#if defined( _X360 ) + + char cmdString[XBX_MAX_RCMDLENGTH]; + + // Before calling VXConsole, init the global variable that receives the result + g_VXConsoleAssertReturnValue = -1; + + // Message VXConsole to pop up a PC-side Assert dialog + _snprintf( cmdString, sizeof(cmdString), "Assert() 0x%.8x File: %s\tLine: %d\t%s", + &g_VXConsoleAssertReturnValue, pFilename, line, pExpression ); + XBX_SendRemoteCommand( cmdString, false ); + + // We sent a synchronous message, so g_xbx_dbgVXConsoleAssertReturnValue should have been overwritten by now + if ( g_VXConsoleAssertReturnValue == -1 ) + { + // VXConsole isn't connected/running - default to the old behaviour (break) + g_bBreak = true; + } + else + { + // Respond to what the user selected + switch( g_VXConsoleAssertReturnValue ) + { + case ASSERT_ACTION_IGNORE_FILE: + IgnoreAssertsInCurrentFile(); + break; + case ASSERT_ACTION_IGNORE_THIS: + // Ignore this Assert once + break; + case ASSERT_ACTION_BREAK: + // Break on this Assert + g_bBreak = true; + break; + case ASSERT_ACTION_IGNORE_ALL: + // Ignore all Asserts from now on + g_bAssertsEnabled = false; + break; + case ASSERT_ACTION_IGNORE_ALWAYS: + // Ignore this Assert from now on + IgnoreAssertsNearby( 0 ); + break; + case ASSERT_ACTION_OTHER: + default: + // Error... just break + XBX_Error( "DoNewAssertDialog: invalid Assert response returned from VXConsole - breaking to debugger" ); + g_bBreak = true; + break; + } + } + +#elif defined( _WIN32 ) + + if ( !ThreadInMainThread() ) + { + int result = MessageBox( NULL, pExpression, "Assertion Failed", MB_SYSTEMMODAL | MB_CANCELTRYCONTINUE ); + + if ( result == IDCANCEL ) + { + IgnoreAssertsNearby( 0 ); + } + else if ( result == IDCONTINUE ) + { + g_bBreak = true; + } + } + else + { + HWND hParentWindow = FindLikelyParentWindow(); + + DialogBox( g_hTier0Instance, MAKEINTRESOURCE( IDD_ASSERT_DIALOG ), hParentWindow, AssertDialogProc ); + } + +#elif defined( POSIX ) + static FUNC_SDL_ShowMessageBox *pfnSDLShowMessageBox = NULL; + if( !pfnSDLShowMessageBox ) + { +#ifdef OSX + void *ret = dlopen( "libSDL2-2.0.0.dylib", RTLD_LAZY ); +#else + void *ret = dlopen( "libSDL2-2.0.so.0", RTLD_LAZY ); +#endif + if ( ret ) + { pfnSDLShowMessageBox = ( FUNC_SDL_ShowMessageBox * )dlsym( ret, "SDL_ShowMessageBox" ); } + } + + if( pfnSDLShowMessageBox ) + { + int buttonid; + char text[ 4096 ]; + SDL_MessageBoxData messageboxdata = { 0 }; + const char *DefaultAction = Plat_IsInDebugSession() ? "Break" : "Corefile"; + SDL_MessageBoxButtonData buttondata[] = + { + { SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, IDC_BREAK, DefaultAction }, + { SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, IDC_IGNORE_THIS, "Ignore" }, + { 0, IDC_IGNORE_FILE, "Ignore This File" }, + { 0, IDC_IGNORE_ALWAYS, "Always Ignore" }, + { 0, IDC_IGNORE_ALL, "Ignore All Asserts" }, + }; + + _snprintf( text, sizeof( text ), "File: %s\nLine: %i\nExpr: %s\n", pFilename, line, pExpression ); + text[ sizeof( text ) - 1 ] = 0; + + messageboxdata.window = g_SDLWindow; + messageboxdata.title = "Assertion Failed"; + messageboxdata.message = text; + messageboxdata.numbuttons = ARRAYSIZE( buttondata ); + messageboxdata.buttons = buttondata; + + int Ret = ( *pfnSDLShowMessageBox )( &messageboxdata, &buttonid ); + if( Ret == -1 ) + { + buttonid = IDC_BREAK; + } + + switch( buttonid ) + { + default: + case IDC_BREAK: + // Break on this Assert + g_bBreak = true; + break; + case IDC_IGNORE_THIS: + // Ignore this Assert once + break; + case IDC_IGNORE_FILE: + IgnoreAssertsInCurrentFile(); + break; + case IDC_IGNORE_ALWAYS: + // Ignore this Assert from now on + IgnoreAssertsNearby( 0 ); + break; + case IDC_IGNORE_ALL: + // Ignore all Asserts from now on + g_bAssertsEnabled = false; + break; + } + } + else + { + // Couldn't SDL it up + g_bBreak = true; + } + +#else + // No dialog mode on this platform + g_bBreak = true; +#endif + + return g_bBreak; +} + diff --git a/tier0/assert_dialog.rc b/tier0/assert_dialog.rc new file mode 100644 index 0000000..19b7982 --- /dev/null +++ b/tier0/assert_dialog.rc @@ -0,0 +1,124 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ASSERT_DIALOG DIALOG DISCARDABLE 0, 0, 268, 158 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Assert" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "File:",IDC_NOID,7,7,23,9 + LTEXT "c:/hl2/src/blah.cpp",IDC_FILENAME_CONTROL,36,7,217,8 + LTEXT "Line:",IDC_NOID,7,18,23,9 + LTEXT "45",IDC_LINE_CONTROL,36,18,217,8 + LTEXT "Assert:",IDC_NOID,7,29,23,9 + CONTROL "ASSERT MESSAGE",IDC_ASSERT_MSG_CTRL,"Static", + SS_LEFTNOWORDWRAP | SS_NOPREFIX | WS_GROUP,36,29,217,9 + DEFPUSHBUTTON "&Break in Debugger",IDC_BREAK,7,49,98,14 + PUSHBUTTON "&Ignore This Assert",IDC_IGNORE_THIS,7,66,98,14 + EDITTEXT IDC_IGNORE_NUMTIMES,110,66,24,14,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "time(s).",IDC_NOID,138,68,23,8 + PUSHBUTTON "Always Ignore &This Assert",IDC_IGNORE_ALWAYS,7,84,98, + 14 + PUSHBUTTON "Ignore &Nearby Asserts",IDC_IGNORE_NEARBY,7,102,98,14 + LTEXT "within",IDC_NOID,109,105,19,8 + EDITTEXT IDC_IGNORE_NUMLINES,131,102,40,14,ES_AUTOHSCROLL | + ES_NUMBER + LTEXT "lines.",IDC_NOID,175,105,17,8 + PUSHBUTTON "Ignore Asserts in This &File",IDC_IGNORE_FILE,7,120,98, + 14 + PUSHBUTTON "Ignore &All Asserts",IDC_IGNORE_ALL,7,137,98,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_ASSERT_DIALOG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 261 + TOPMARGIN, 7 + BOTTOMMARGIN, 151 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// ETW event manifest data from ValveETWProviders.man +// + +#include "ValveETWProviderEvents.rc" diff --git a/tier0/commandline.cpp b/tier0/commandline.cpp new file mode 100644 index 0000000..b2ab532 --- /dev/null +++ b/tier0/commandline.cpp @@ -0,0 +1,696 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// + +#include "pch_tier0.h" +#include "tier0/icommandline.h" + +#include +#include +#include +#include +#include "tier0/dbg.h" + +#include "tier0/memdbgon.h" + +#ifdef POSIX +#include +#define _MAX_PATH PATH_MAX +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +static const int MAX_PARAMETER_LEN = 128; + +//----------------------------------------------------------------------------- +// Purpose: Implements ICommandLine +//----------------------------------------------------------------------------- +class CCommandLine : public ICommandLine +{ +public: + // Construction + CCommandLine( void ); + virtual ~CCommandLine( void ); + + // Implements ICommandLine + virtual void CreateCmdLine( const char *commandline ); + virtual void CreateCmdLine( int argc, char **argv ); + virtual const char *GetCmdLine( void ) const; + virtual const char *CheckParm( const char *psz, const char **ppszValue = 0 ) const; + // A bool return of whether param exists, useful for just checking if param that is just a flag is set + virtual bool HasParm( const char *psz ) const; + + virtual void RemoveParm( const char *parm ); + virtual void AppendParm( const char *pszParm, const char *pszValues ); + + virtual int ParmCount() const; + virtual int FindParm( const char *psz ) const; + virtual const char* GetParm( int nIndex ) const; + + virtual const char *ParmValue( const char *psz, const char *pDefaultVal = NULL ) const OVERRIDE; + virtual int ParmValue( const char *psz, int nDefaultVal ) const OVERRIDE; + virtual float ParmValue( const char *psz, float flDefaultVal ) const OVERRIDE; + virtual const char *ParmValueByIndex( int nIndex, const char *pDefaultVal = 0 ) const OVERRIDE; + + virtual void SetParm( int nIndex, char const *pParm ); + +private: + enum + { + MAX_PARAMETER_LEN = 128, + MAX_PARAMETERS = 256, + }; + + // When the commandline contains @name, it reads the parameters from that file + void LoadParametersFromFile( const char *&pSrc, char *&pDst, int maxDestLen, bool bInQuotes ); + + // Parse command line... + void ParseCommandLine(); + + // Frees the command line arguments + void CleanUpParms(); + + // Adds an argument.. + void AddArgument( const char *pFirst, const char *pLast ); + + // Copy of actual command line + char *m_pszCmdLine; + + // Pointers to each argument... + int m_nParmCount; + char *m_ppParms[MAX_PARAMETERS]; +}; + + +//----------------------------------------------------------------------------- +// Instance singleton and expose interface to rest of code +//----------------------------------------------------------------------------- +static CCommandLine g_CmdLine; +ICommandLine *CommandLine() +{ + return &g_CmdLine; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommandLine::CCommandLine( void ) +{ + m_pszCmdLine = NULL; + m_nParmCount = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CCommandLine::~CCommandLine( void ) +{ + CleanUpParms(); + delete[] m_pszCmdLine; +} + + +//----------------------------------------------------------------------------- +// Read commandline from file instead... +//----------------------------------------------------------------------------- +void CCommandLine::LoadParametersFromFile( const char *&pSrc, char *&pDst, int maxDestLen, bool bInQuotes ) +{ + // Suck out the file name + char szFileName[ _MAX_PATH ]; + char *pOut; + char *pDestStart = pDst; + + if ( maxDestLen < 3 ) + return; + + // Skip the @ sign + pSrc++; + + pOut = szFileName; + + char terminatingChar = ' '; + if ( bInQuotes ) + terminatingChar = '\"'; + + while ( *pSrc && *pSrc != terminatingChar ) + { + *pOut++ = *pSrc++; + if ( (pOut - szFileName) >= (_MAX_PATH-1) ) + break; + } + + *pOut = '\0'; + + // Skip the space after the file name + if ( *pSrc ) + pSrc++; + + // Now read in parameters from file + FILE *fp = fopen( szFileName, "r" ); + if ( fp ) + { + char c; + c = (char)fgetc( fp ); + while ( c != EOF ) + { + // Turn return characters into spaces + if ( c == '\n' ) + c = ' '; + + *pDst++ = c; + + // Don't go past the end, and allow for our terminating space character AND a terminating null character. + if ( (pDst - pDestStart) >= (maxDestLen-2) ) + break; + + // Get the next character, if there are more + c = (char)fgetc( fp ); + } + + // Add a terminating space character + *pDst++ = ' '; + + fclose( fp ); + } + else + { + printf( "Parameter file '%s' not found, skipping...", szFileName ); + } +} + + +//----------------------------------------------------------------------------- +// Creates a command line from the arguments passed in +//----------------------------------------------------------------------------- +void CCommandLine::CreateCmdLine( int argc, char **argv ) +{ + char cmdline[ 2048 ]; + cmdline[ 0 ] = 0; + + char *dest = cmdline; + size_t size = sizeof( cmdline ); + const char *space = ""; + + for ( int i = 0; i < argc; ++i ) + { + // We need room for: space, arg, 2 quotes, and a nil. + Assert( strlen( space ) + strlen( argv[ i ] ) + 2 + 1 <= size ); + + if ( size ) + { + _snprintf( dest, size, "%s\"%s\"", space, argv[ i ] ); + dest[ size - 1 ] = 0; + } + + size_t len = strlen( dest ); + size -= len; + dest += len; + space = " "; + } + + CreateCmdLine( cmdline ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Create a command line from the passed in string +// Note that if you pass in a @filename, then the routine will read settings +// from a file instead of the command line +//----------------------------------------------------------------------------- +void CCommandLine::CreateCmdLine( const char *commandline ) +{ + if ( m_pszCmdLine ) + { + delete[] m_pszCmdLine; + } + + char szFull[ 4096 ]; + szFull[0] = '\0'; + + char *pDst = szFull; + const char *pSrc = commandline; + + bool bInQuotes = false; + const char *pInQuotesStart = 0; + while ( *pSrc ) + { + // Is this an unslashed quote? + if ( *pSrc == '"' ) + { + if ( pSrc == commandline || ( pSrc[-1] != '/' && pSrc[-1] != '\\' ) ) + { + bInQuotes = !bInQuotes; + pInQuotesStart = pSrc + 1; + } + } + + if ( *pSrc == '@' ) + { + if ( pSrc == commandline || (!bInQuotes && isspace( pSrc[-1] )) || (bInQuotes && pSrc == pInQuotesStart) ) + { + LoadParametersFromFile( pSrc, pDst, sizeof( szFull ) - (pDst - szFull), bInQuotes ); + continue; + } + } + + // Don't go past the end. + if ( (pDst - szFull) >= (sizeof( szFull ) - 1) ) + break; + + *pDst++ = *pSrc++; + } + + *pDst = '\0'; + + int len = strlen( szFull ) + 1; + m_pszCmdLine = new char[len]; + memcpy( m_pszCmdLine, szFull, len ); + + ParseCommandLine(); +} + + +//----------------------------------------------------------------------------- +// Finds a string in another string with a case insensitive test +//----------------------------------------------------------------------------- +static char * _stristr( char * pStr, const char * pSearch ) +{ + AssertValidStringPtr(pStr); + AssertValidStringPtr(pSearch); + + if (!pStr || !pSearch) + return 0; + + char* pLetter = pStr; + + // Check the entire string + while (*pLetter != 0) + { + // Skip over non-matches + if (tolower((unsigned char)*pLetter) == tolower((unsigned char)*pSearch)) + { + // Check for match + char const* pMatch = pLetter + 1; + char const* pTest = pSearch + 1; + while (*pTest != 0) + { + // We've run off the end; don't bother. + if (*pMatch == 0) + return 0; + + if (tolower((unsigned char)*pMatch) != tolower((unsigned char)*pTest)) + break; + + ++pMatch; + ++pTest; + } + + // Found a match! + if (*pTest == 0) + return pLetter; + } + + ++pLetter; + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove specified string ( and any args attached to it ) from command line +// Input : *pszParm - +//----------------------------------------------------------------------------- +void CCommandLine::RemoveParm( const char *pszParm ) +{ + if ( !m_pszCmdLine ) + return; + + // Search for first occurrence of pszParm + char *p, *found; + char *pnextparam; + int n; + int curlen; + + p = m_pszCmdLine; + while ( *p ) + { + curlen = strlen( p ); + + found = _stristr( p, pszParm ); + if ( !found ) + break; + + pnextparam = found + 1; + bool bHadQuote = false; + if ( found > m_pszCmdLine && found[-1] == '\"' ) + bHadQuote = true; + + while ( pnextparam && *pnextparam && (*pnextparam != ' ') && (*pnextparam != '\"') ) + pnextparam++; + + if ( pnextparam && ( static_cast( pnextparam - found ) > strlen( pszParm ) ) ) + { + p = pnextparam; + continue; + } + + while ( pnextparam && *pnextparam && (*pnextparam != '-') && (*pnextparam != '+') ) + pnextparam++; + + if ( bHadQuote ) + { + found--; + } + + if ( pnextparam && *pnextparam ) + { + // We are either at the end of the string, or at the next param. Just chop out the current param. + n = curlen - ( pnextparam - p ); // # of characters after this param. + memmove( found, pnextparam, n ); + + found[n] = '\0'; + } + else + { + // Clear out rest of string. + n = pnextparam - found; + memset( found, 0, n ); + } + } + + // Strip and trailing ' ' characters left over. + while ( 1 ) + { + int len = strlen( m_pszCmdLine ); + if ( len == 0 || m_pszCmdLine[ len - 1 ] != ' ' ) + break; + + m_pszCmdLine[len - 1] = '\0'; + } + + ParseCommandLine(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Append parameter and argument values to command line +// Input : *pszParm - +// *pszValues - +//----------------------------------------------------------------------------- +void CCommandLine::AppendParm( const char *pszParm, const char *pszValues ) +{ + int nNewLength = 0; + char *pCmdString; + + nNewLength = strlen( pszParm ); // Parameter. + if ( pszValues ) + nNewLength += strlen( pszValues ) + 1; // Values + leading space character. + nNewLength++; // Terminal 0; + + if ( !m_pszCmdLine ) + { + m_pszCmdLine = new char[ nNewLength ]; + strcpy( m_pszCmdLine, pszParm ); + if ( pszValues ) + { + strcat( m_pszCmdLine, " " ); + strcat( m_pszCmdLine, pszValues ); + } + + ParseCommandLine(); + return; + } + + // Remove any remnants from the current Cmd Line. + RemoveParm( pszParm ); + + nNewLength += strlen( m_pszCmdLine ) + 1 + 1; + + pCmdString = new char[ nNewLength ]; + memset( pCmdString, 0, nNewLength ); + + strcpy ( pCmdString, m_pszCmdLine ); // Copy old command line. + strcat ( pCmdString, " " ); // Put in a space + strcat ( pCmdString, pszParm ); + if ( pszValues ) + { + strcat( pCmdString, " " ); + strcat( pCmdString, pszValues ); + } + + // Kill off the old one + delete[] m_pszCmdLine; + + // Point at the new command line. + m_pszCmdLine = pCmdString; + + ParseCommandLine(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Return current command line +// Output : const char +//----------------------------------------------------------------------------- +const char *CCommandLine::GetCmdLine( void ) const +{ + return m_pszCmdLine; +} + + +//----------------------------------------------------------------------------- +// Purpose: Search for the parameter in the current commandline +// Input : *psz - +// **ppszValue - +// Output : char +//----------------------------------------------------------------------------- +const char *CCommandLine::CheckParm( const char *psz, const char **ppszValue ) const +{ + if ( ppszValue ) + *ppszValue = NULL; + + int i = FindParm( psz ); + if ( i == 0 ) + return NULL; + + if ( ppszValue ) + { + if ( (i+1) >= m_nParmCount ) + { + *ppszValue = NULL; + } + else + { + *ppszValue = m_ppParms[i+1]; + } + } + + return m_ppParms[i]; +} + + +//----------------------------------------------------------------------------- +// Adds an argument.. +//----------------------------------------------------------------------------- +void CCommandLine::AddArgument( const char *pFirst, const char *pLast ) +{ + if ( pLast <= pFirst ) + return; + + if ( m_nParmCount >= MAX_PARAMETERS ) + Error( "CCommandLine::AddArgument: exceeded %d parameters", MAX_PARAMETERS ); + + size_t nLen = pLast - pFirst + 1; + m_ppParms[m_nParmCount] = new char[nLen]; + memcpy( m_ppParms[m_nParmCount], pFirst, nLen - 1 ); + m_ppParms[m_nParmCount][nLen - 1] = 0; + + ++m_nParmCount; +} + + +//----------------------------------------------------------------------------- +// Parse command line... +//----------------------------------------------------------------------------- +void CCommandLine::ParseCommandLine() +{ + CleanUpParms(); + if (!m_pszCmdLine) + return; + + const char *pChar = m_pszCmdLine; + while ( *pChar && isspace(*pChar) ) + { + ++pChar; + } + + bool bInQuotes = false; + const char *pFirstLetter = NULL; + for ( ; *pChar; ++pChar ) + { + if ( bInQuotes ) + { + if ( *pChar != '\"' ) + continue; + + AddArgument( pFirstLetter, pChar ); + pFirstLetter = NULL; + bInQuotes = false; + continue; + } + + // Haven't started a word yet... + if ( !pFirstLetter ) + { + if ( *pChar == '\"' ) + { + bInQuotes = true; + pFirstLetter = pChar + 1; + continue; + } + + if ( isspace( *pChar ) ) + continue; + + pFirstLetter = pChar; + continue; + } + + // Here, we're in the middle of a word. Look for the end of it. + if ( isspace( *pChar ) ) + { + AddArgument( pFirstLetter, pChar ); + pFirstLetter = NULL; + } + } + + if ( pFirstLetter ) + { + AddArgument( pFirstLetter, pChar ); + } +} + + +//----------------------------------------------------------------------------- +// Individual command line arguments +//----------------------------------------------------------------------------- +void CCommandLine::CleanUpParms() +{ + for ( int i = 0; i < m_nParmCount; ++i ) + { + delete [] m_ppParms[i]; + m_ppParms[i] = NULL; + } + m_nParmCount = 0; +} + + +//----------------------------------------------------------------------------- +// Returns individual command line arguments +//----------------------------------------------------------------------------- +int CCommandLine::ParmCount() const +{ + return m_nParmCount; +} + +int CCommandLine::FindParm( const char *psz ) const +{ + // Start at 1 so as to not search the exe name + for ( int i = 1; i < m_nParmCount; ++i ) + { + if ( !_stricmp( psz, m_ppParms[i] ) ) + return i; + } + return 0; +} + +bool CCommandLine::HasParm( const char *psz ) const +{ + return ( FindParm( psz ) != 0 ); +} + +const char* CCommandLine::GetParm( int nIndex ) const +{ + Assert( (nIndex >= 0) && (nIndex < m_nParmCount) ); + if ( (nIndex < 0) || (nIndex >= m_nParmCount) ) + return ""; + return m_ppParms[nIndex]; +} +void CCommandLine::SetParm( int nIndex, char const *pParm ) +{ + if ( pParm ) + { + Assert( (nIndex >= 0) && (nIndex < m_nParmCount) ); + if ( (nIndex >= 0) && (nIndex < m_nParmCount) ) + { + if ( m_ppParms[nIndex] ) + delete[] m_ppParms[nIndex]; + m_ppParms[nIndex] = strdup( pParm ); + } + + } + +} + + +//----------------------------------------------------------------------------- +// Returns the argument after the one specified, or the default if not found +//----------------------------------------------------------------------------- +const char *CCommandLine::ParmValue( const char *psz, const char *pDefaultVal ) const +{ + int nIndex = FindParm( psz ); + if (( nIndex == 0 ) || (nIndex == m_nParmCount - 1)) + return pDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with '+' or '-' + if ( m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+' ) + return pDefaultVal; + + return m_ppParms[nIndex + 1]; +} + +int CCommandLine::ParmValue( const char *psz, int nDefaultVal ) const +{ + int nIndex = FindParm( psz ); + if (( nIndex == 0 ) || (nIndex == m_nParmCount - 1)) + return nDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with '+' or '-' + if ( m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+' ) + return nDefaultVal; + + return atoi( m_ppParms[nIndex + 1] ); +} + +float CCommandLine::ParmValue( const char *psz, float flDefaultVal ) const +{ + int nIndex = FindParm( psz ); + if (( nIndex == 0 ) || (nIndex == m_nParmCount - 1)) + return flDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with '+' or '-' + if ( m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+' ) + return flDefaultVal; + + return atof( m_ppParms[nIndex + 1] ); +} +const char *CCommandLine::ParmValueByIndex( int nIndex, const char *pDefaultVal ) const +{ + if (( nIndex == 0 ) || (nIndex == m_nParmCount - 1)) + return pDefaultVal; + + // Probably another cmdline parameter instead of a valid arg if it starts with '+' or '-' + if ( m_ppParms[nIndex + 1][0] == '-' || m_ppParms[nIndex + 1][0] == '+' ) + return pDefaultVal; + + return m_ppParms[nIndex + 1]; +} + diff --git a/tier0/cpu.cpp b/tier0/cpu.cpp new file mode 100644 index 0000000..63c89d2 --- /dev/null +++ b/tier0/cpu.cpp @@ -0,0 +1,596 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// +#include "pch_tier0.h" + +#if defined(_WIN32) && !defined(_X360) +#define WINDOWS_LEAN_AND_MEAN +#include +#elif defined(_LINUX) +#include +#elif defined(OSX) +#include +#endif + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +const tchar* GetProcessorVendorId(); + +static bool cpuid(unsigned long function, unsigned long& out_eax, unsigned long& out_ebx, unsigned long& out_ecx, unsigned long& out_edx) +{ +#if defined(GNUC) + asm("mov %%ebx, %%esi\n\t" + "cpuid\n\t" + "xchg %%esi, %%ebx" + : "=a" (out_eax), + "=S" (out_ebx), + "=c" (out_ecx), + "=d" (out_edx) + : "a" (function) + ); + return true; +#elif defined( _X360 ) + return false; +#elif defined(_WIN64) + int pCPUInfo[4]; + __cpuid( pCPUInfo, (int)function ); + out_eax = pCPUInfo[0]; + out_ebx = pCPUInfo[1]; + out_ecx = pCPUInfo[2]; + out_edx = pCPUInfo[3]; + return true; +#else + bool retval = true; + unsigned long local_eax, local_ebx, local_ecx, local_edx; + _asm pushad; + + __try + { + _asm + { + xor edx, edx // Clue the compiler that EDX is about to be used. + mov eax, function // set up CPUID to return processor version and features + // 0 = vendor string, 1 = version info, 2 = cache info + cpuid // code bytes = 0fh, 0a2h + mov local_eax, eax // features returned in eax + mov local_ebx, ebx // features returned in ebx + mov local_ecx, ecx // features returned in ecx + mov local_edx, edx // features returned in edx + } + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + retval = false; + } + + out_eax = local_eax; + out_ebx = local_ebx; + out_ecx = local_ecx; + out_edx = local_edx; + + _asm popad + + return retval; +#endif +} + +static bool CheckMMXTechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return true; +#else + unsigned long eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & 0x800000 ) != 0; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: This is a bit of a hack because it appears +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +static bool IsWin98OrOlder() +{ +#if defined( _X360 ) || defined( _PS3 ) || defined( POSIX ) + return false; +#else + bool retval = false; + + OSVERSIONINFOEX osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + + BOOL bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi); + if( !bOsVersionInfoEx ) + { + // If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO. + + osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO); + if ( !GetVersionEx ( (OSVERSIONINFO *) &osvi) ) + { + Error( _T("IsWin98OrOlder: Unable to get OS version information") ); + } + } + + switch (osvi.dwPlatformId) + { + case VER_PLATFORM_WIN32_NT: + // NT, XP, Win2K, etc. all OK for SSE + break; + case VER_PLATFORM_WIN32_WINDOWS: + // Win95, 98, Me can't do SSE + retval = true; + break; + case VER_PLATFORM_WIN32s: + // Can't really run this way I don't think... + retval = true; + break; + default: + break; + } + + return retval; +#endif +} + + +static bool CheckSSETechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return true; +#else + if ( IsWin98OrOlder() ) + { + return false; + } + + unsigned long eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + { + return false; + } + + return ( edx & 0x2000000L ) != 0; +#endif +} + +static bool CheckSSE2Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + unsigned long eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & 0x04000000 ) != 0; +#endif +} + +bool CheckSSE3Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + unsigned long eax,ebx,edx,ecx; + if( !cpuid(1,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & 0x00000001 ) != 0; // bit 1 of ECX +#endif +} + +bool CheckSSSE3Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + // SSSE 3 is implemented by both Intel and AMD + // detection is done the same way for both vendors + unsigned long eax,ebx,edx,ecx; + if( !cpuid(1,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & ( 1 << 9 ) ) != 0; // bit 9 of ECX +#endif +} + +bool CheckSSE41Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + // SSE 4.1 is implemented by both Intel and AMD + // detection is done the same way for both vendors + + unsigned long eax,ebx,edx,ecx; + if( !cpuid(1,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & ( 1 << 19 ) ) != 0; // bit 19 of ECX +#endif +} + +bool CheckSSE42Technology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + // SSE4.2 is an Intel-only feature + + const char *pchVendor = GetProcessorVendorId(); + if ( 0 != V_tier0_stricmp( pchVendor, "GenuineIntel" ) ) + return false; + + unsigned long eax,ebx,edx,ecx; + if( !cpuid(1,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & ( 1 << 20 ) ) != 0; // bit 20 of ECX +#endif +} + + +bool CheckSSE4aTechnology( void ) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + // SSE 4a is an AMD-only feature + + const char *pchVendor = GetProcessorVendorId(); + if ( 0 != V_tier0_stricmp( pchVendor, "AuthenticAMD" ) ) + return false; + + unsigned long eax,ebx,edx,ecx; + if( !cpuid( 0x80000001,eax,ebx,ecx,edx) ) + return false; + + return ( ecx & ( 1 << 6 ) ) != 0; // bit 6 of ECX +#endif +} + + +static bool Check3DNowTechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + unsigned long eax, unused; + if ( !cpuid(0x80000000,eax,unused,unused,unused) ) + return false; + + if ( eax > 0x80000000L ) + { + if ( !cpuid(0x80000001,unused,unused,unused,eax) ) + return false; + + return ( eax & 1<<31 ) != 0; + } + return false; +#endif +} + +static bool CheckCMOVTechnology() +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + unsigned long eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & (1<<15) ) != 0; +#endif +} + +static bool CheckFCMOVTechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + unsigned long eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & (1<<16) ) != 0; +#endif +} + +static bool CheckRDTSCTechnology(void) +{ +#if defined( _X360 ) || defined( _PS3 ) + return false; +#else + unsigned long eax,ebx,edx,unused; + if ( !cpuid(1,eax,ebx,unused,edx) ) + return false; + + return ( edx & 0x10 ) != 0; +#endif +} + +// Return the Processor's vendor identification string, or "Generic_x86" if it doesn't exist on this CPU +const tchar* GetProcessorVendorId() +{ +#if defined( _X360 ) || defined( _PS3 ) + return "PPC"; +#else + unsigned long unused, VendorIDRegisters[3]; + + static tchar VendorID[13]; + + memset( VendorID, 0, sizeof(VendorID) ); + if ( !cpuid(0,unused, VendorIDRegisters[0], VendorIDRegisters[2], VendorIDRegisters[1] ) ) + { + if ( IsPC() ) + { + _tcscpy( VendorID, _T( "Generic_x86" ) ); + } + else if ( IsX360() ) + { + _tcscpy( VendorID, _T( "PowerPC" ) ); + } + } + else + { + memcpy( VendorID+0, &(VendorIDRegisters[0]), sizeof( VendorIDRegisters[0] ) ); + memcpy( VendorID+4, &(VendorIDRegisters[1]), sizeof( VendorIDRegisters[1] ) ); + memcpy( VendorID+8, &(VendorIDRegisters[2]), sizeof( VendorIDRegisters[2] ) ); + } + + return VendorID; +#endif +} + +// Returns non-zero if Hyper-Threading Technology is supported on the processors and zero if not. This does not mean that +// Hyper-Threading Technology is necessarily enabled. +static bool HTSupported(void) +{ +#if defined( _X360 ) + // not entirtely sure about the semantic of HT support, it being an intel name + // are we asking about HW threads or HT? + return true; +#else + const unsigned int HT_BIT = 0x10000000; // EDX[28] - Bit 28 set indicates Hyper-Threading Technology is supported in hardware. + const unsigned int FAMILY_ID = 0x0f00; // EAX[11:8] - Bit 11 thru 8 contains family processor id + const unsigned int EXT_FAMILY_ID = 0x0f00000; // EAX[23:20] - Bit 23 thru 20 contains extended family processor id + const unsigned int PENTIUM4_ID = 0x0f00; // Pentium 4 family processor id + + unsigned long unused, + reg_eax = 0, + reg_edx = 0, + vendor_id[3] = {0, 0, 0}; + + // verify cpuid instruction is supported + if( !cpuid(0,unused, vendor_id[0],vendor_id[2],vendor_id[1]) + || !cpuid(1,reg_eax,unused,unused,reg_edx) ) + return false; + + // Check to see if this is a Pentium 4 or later processor + if (((reg_eax & FAMILY_ID) == PENTIUM4_ID) || (reg_eax & EXT_FAMILY_ID)) + if (vendor_id[0] == 'uneG' && vendor_id[1] == 'Ieni' && vendor_id[2] == 'letn') + return (reg_edx & HT_BIT) != 0; // Genuine Intel Processor with Hyper-Threading Technology + + return false; // This is not a genuine Intel processor. +#endif +} + +// Returns the number of logical processors per physical processors. +static uint8 LogicalProcessorsPerPackage(void) +{ +#if defined( _X360 ) + return 2; +#else + // EBX[23:16] indicate number of logical processors per package + const unsigned NUM_LOGICAL_BITS = 0x00FF0000; + + unsigned long unused, reg_ebx = 0; + + if ( !HTSupported() ) + return 1; + + if ( !cpuid(1,unused,reg_ebx,unused,unused) ) + return 1; + + return (uint8) ((reg_ebx & NUM_LOGICAL_BITS) >> 16); +#endif +} + +#if defined(POSIX) +// Move this declaration out of the CalculateClockSpeed() function because +// otherwise clang warns that it is non-obvious whether it is a variable +// or a function declaration: [-Wvexing-parse] +uint64 CalculateCPUFreq(); // from cpu_linux.cpp +#endif + +// Measure the processor clock speed by sampling the cycle count, waiting +// for some fraction of a second, then measuring the elapsed number of cycles. +static int64 CalculateClockSpeed() +{ +#if defined( _WIN32 ) +#if !defined( _X360 ) + LARGE_INTEGER waitTime, startCount, curCount; + CCycleCount start, end; + + // Take 1/32 of a second for the measurement. + QueryPerformanceFrequency( &waitTime ); + int scale = 5; + waitTime.QuadPart >>= scale; + + QueryPerformanceCounter( &startCount ); + start.Sample(); + do + { + QueryPerformanceCounter( &curCount ); + } + while ( curCount.QuadPart - startCount.QuadPart < waitTime.QuadPart ); + end.Sample(); + + int64 freq = (end.m_Int64 - start.m_Int64) << scale; + if ( freq == 0 ) + { + // Steam was seeing Divide-by-zero crashes on some Windows machines due to + // WIN64_AMD_DUALCORE_TIMER_WORKAROUND that can cause rdtsc to effectively + // stop. Staging doesn't have the workaround but I'm checking in the fix + // anyway. Return a plausible speed and get on with our day. + freq = 2000000000; + } + return freq; + +#else + return 3200000000LL; +#endif +#elif defined(POSIX) + int64 freq =(int64)CalculateCPUFreq(); + if ( freq == 0 ) // couldn't calculate clock speed + { + Error( "Unable to determine CPU Frequency\n" ); + } + return freq; +#endif +} + +const CPUInformation* GetCPUInformation() +{ + static CPUInformation pi; + + // Has the structure already been initialized and filled out? + if ( pi.m_Size == sizeof(pi) ) + return π + + // Redundant, but just in case the user somehow messes with the size. + memset(&pi, 0x0, sizeof(pi)); + + // Fill out the structure, and return it: + pi.m_Size = sizeof(pi); + + // Grab the processor frequency: + pi.m_Speed = CalculateClockSpeed(); + + // Get the logical and physical processor counts: + pi.m_nLogicalProcessors = LogicalProcessorsPerPackage(); + +#if defined(_WIN32) && !defined( _X360 ) + SYSTEM_INFO si; + ZeroMemory( &si, sizeof(si) ); + + GetSystemInfo( &si ); + + pi.m_nPhysicalProcessors = (unsigned char)(si.dwNumberOfProcessors / pi.m_nLogicalProcessors); + pi.m_nLogicalProcessors = (unsigned char)(pi.m_nLogicalProcessors * pi.m_nPhysicalProcessors); + + // Make sure I always report at least one, when running WinXP with the /ONECPU switch, + // it likes to report 0 processors for some reason. + if ( pi.m_nPhysicalProcessors == 0 && pi.m_nLogicalProcessors == 0 ) + { + pi.m_nPhysicalProcessors = 1; + pi.m_nLogicalProcessors = 1; + } +#elif defined( _X360 ) + pi.m_nPhysicalProcessors = 3; + pi.m_nLogicalProcessors = 6; +#elif defined(_LINUX) + // TODO: poll /dev/cpuinfo when we have some benefits from multithreading + FILE *fpCpuInfo = fopen( "/proc/cpuinfo", "r" ); + if ( fpCpuInfo ) + { + int nLogicalProcs = 0; + int nProcId = -1, nCoreId = -1; + const int kMaxPhysicalCores = 128; + int anKnownIds[kMaxPhysicalCores]; + int nKnownIdCount = 0; + char buf[255]; + while ( fgets( buf, ARRAYSIZE(buf), fpCpuInfo ) ) + { + if ( char *value = strchr( buf, ':' ) ) + { + for ( char *p = value - 1; p > buf && isspace((unsigned char)*p); --p ) + { + *p = 0; + } + for ( char *p = buf; p < value && *p; ++p ) + { + *p = tolower((unsigned char)*p); + } + if ( !strcmp( buf, "processor" ) ) + { + ++nLogicalProcs; + nProcId = nCoreId = -1; + } + else if ( !strcmp( buf, "physical id" ) ) + { + nProcId = atoi( value+1 ); + } + else if ( !strcmp( buf, "core id" ) ) + { + nCoreId = atoi( value+1 ); + } + + if (nProcId != -1 && nCoreId != -1) // as soon as we have a complete id, process it + { + int i = 0, nId = (nProcId << 16) + nCoreId; + while ( i < nKnownIdCount && anKnownIds[i] != nId ) { ++i; } + if ( i == nKnownIdCount && nKnownIdCount < kMaxPhysicalCores ) + anKnownIds[nKnownIdCount++] = nId; + nProcId = nCoreId = -1; + } + } + } + fclose( fpCpuInfo ); + pi.m_nLogicalProcessors = MAX( 1, nLogicalProcs ); + pi.m_nPhysicalProcessors = MAX( 1, nKnownIdCount ); + } + else + { + pi.m_nPhysicalProcessors = 1; + pi.m_nLogicalProcessors = 1; + Assert( !"couldn't read cpu information from /proc/cpuinfo" ); + } +#elif defined(OSX) + int mib[2], num_cpu = 1; + size_t len; + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + len = sizeof(num_cpu); + sysctl(mib, 2, &num_cpu, &len, NULL, 0); + pi.m_nPhysicalProcessors = num_cpu; + pi.m_nLogicalProcessors = num_cpu; +#endif + + // Determine Processor Features: + pi.m_bRDTSC = CheckRDTSCTechnology(); + pi.m_bCMOV = CheckCMOVTechnology(); + pi.m_bFCMOV = CheckFCMOVTechnology(); + pi.m_bMMX = CheckMMXTechnology(); + pi.m_bSSE = CheckSSETechnology(); + pi.m_bSSE2 = CheckSSE2Technology(); + pi.m_bSSE3 = CheckSSE3Technology(); + pi.m_bSSSE3 = CheckSSSE3Technology(); + pi.m_bSSE4a = CheckSSE4aTechnology(); + pi.m_bSSE41 = CheckSSE41Technology(); + pi.m_bSSE42 = CheckSSE42Technology(); + pi.m_b3DNow = Check3DNowTechnology(); + pi.m_szProcessorID = (tchar*)GetProcessorVendorId(); + pi.m_bHT = HTSupported(); + + unsigned long eax, ebx, edx, ecx; + if (cpuid(1, eax, ebx, ecx, edx)) + { + pi.m_nModel = eax; // full CPU model info + pi.m_nFeatures[0] = edx; // x87+ features + pi.m_nFeatures[1] = ecx; // sse3+ features + pi.m_nFeatures[2] = ebx; // some additional features + } + + + + return π +} + diff --git a/tier0/cpu_posix.cpp b/tier0/cpu_posix.cpp new file mode 100644 index 0000000..8de899b --- /dev/null +++ b/tier0/cpu_posix.cpp @@ -0,0 +1,177 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: determine CPU speed under linux +// +// $NoKeywords: $ +//=============================================================================// +#include +#include +#include +#include +#include +#include +#include +#include + +#define rdtsc(x) \ + __asm__ __volatile__ ("rdtsc" : "=A" (x)) + +class TimeVal +{ +public: + TimeVal() {} + TimeVal& operator=(const TimeVal &val) { m_TimeVal = val.m_TimeVal; return *this; } + inline double operator-(const TimeVal &left) + { + uint64 left_us = (uint64) left.m_TimeVal.tv_sec * 1000000 + left.m_TimeVal.tv_usec; + uint64 right_us = (uint64) m_TimeVal.tv_sec * 1000000 + m_TimeVal.tv_usec; + uint64 diff_us = right_us - left_us; + return diff_us * ( 1.0 / 1000000.0 ); + } + + timeval m_TimeVal; +}; + +// Compute the positive difference between two 64 bit numbers. +static inline uint64 diff(uint64 v1, uint64 v2) +{ + int64 d = v1 - v2; + if (d >= 0) + return d; + else + return -d; +} + +#ifdef OSX + +// Mac +uint64 GetCPUFreqFromPROC() +{ + int mib[2] = {CTL_HW, HW_CPU_FREQ}; + uint64 frequency = 0; + size_t len = sizeof(frequency); + + if (sysctl(mib, 2, &frequency, &len, NULL, 0) == -1) + return 0; + return frequency; +} + +#else + +// Linux +uint64 GetCPUFreqFromPROC() +{ + double mhz = 0; + char line[1024], *s, search_str[] = "cpu MHz"; + + /* open proc/cpuinfo */ + FILE *fp = fopen( "/proc/cpuinfo", "r" ); + if (fp == NULL) + { + return 0; + } + + /* ignore all lines until we reach MHz information */ + while (fgets(line, 1024, fp) != NULL) + { + if (strstr(line, search_str) != NULL) + { + /* ignore all characters in line up to : */ + for (s = line; *s && (*s != ':'); ++s) + ; + + /* get MHz number */ + if ( *s && ( sscanf( s + 1, "%lf", &mhz) == 1 ) ) + break; + } + } + + fclose(fp); + + return ( uint64 )( mhz * 1000000 ); +} + +#endif + +uint64 CalculateCPUFreq() +{ +#ifdef LINUX + char const *pFreq = getenv( "CPU_MHZ" ); + if ( pFreq ) + { + uint64 retVal = 1000000; + return retVal * atoi( pFreq ); + } +#endif + + // Try to open cpuinfo_max_freq. If the kernel was built with cpu scaling support disabled, this will fail. + FILE *fp = fopen( "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r" ); + if ( fp ) + { + char buf[ 256 ]; + uint64 retVal = 0; + + buf[ 0 ] = 0; + if( fread( buf, 1, ARRAYSIZE( buf ), fp ) ) + { + retVal = ( uint64 )atoll( buf ); + } + fclose(fp); + + if( retVal ) + { + return retVal * 1000; + } + } + + // Compute the period. Loop until we get 3 consecutive periods that + // are the same to within a small error. The error is chosen + // to be +/- 0.02% on a P-200. + const uint64 error = 40000; + const int max_iterations = 600; + int count; + uint64 period, period1 = error * 2, period2 = 0, period3 = 0; + + for (count = 0; count < max_iterations; count++) + { + TimeVal start_time, end_time; + uint64 start_tsc, end_tsc; + + gettimeofday( &start_time.m_TimeVal, 0 ); + rdtsc( start_tsc ); + usleep( 5000 ); // sleep for 5 msec + gettimeofday( &end_time.m_TimeVal, 0 ); + rdtsc( end_tsc ); + + // end_time - start_time calls into the overloaded TimeVal operator- way above, and returns a double. + period3 = ( end_tsc - start_tsc ) / ( end_time - start_time ); + + if (diff ( period1, period2 ) <= error && + diff ( period2, period3 ) <= error && + diff ( period1, period3 ) <= error ) + { + break; + } + + period1 = period2; + period2 = period3; + } + + if ( count == max_iterations ) + { + return GetCPUFreqFromPROC(); // fall back to /proc + } + + // Set the period to the average period measured. + period = ( period1 + period2 + period3 ) / 3; + + // Some Pentiums have broken TSCs that increment very + // slowly or unevenly. + if (period < 10000000) + { + return GetCPUFreqFromPROC(); // fall back to /proc + } + + return period; +} + diff --git a/tier0/cpu_usage.cpp b/tier0/cpu_usage.cpp new file mode 100644 index 0000000..da313a8 --- /dev/null +++ b/tier0/cpu_usage.cpp @@ -0,0 +1,132 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: return the cpu usage as a float value +// +// On win32 this is 0.0 to 1.0 indicating the amount of CPU time used +// On posix its the load avg from the last minute +// +// On win32 you need to call this once in a while. Every few seconds. +// First call returns zero +//=============================================================================// + +#include "pch_tier0.h" +#include "tier0/platform.h" + +#ifdef WIN32 + +#include + +#define SystemBasicInformation 0 +#define SystemPerformanceInformation 2 +#define SystemTimeInformation 3 + +#define Li2Double(x) ((double)((x).HighPart) * 4.294967296E9 + (double)((x).LowPart)) + +typedef struct +{ + DWORD dwUnknown1; + ULONG uKeMaximumIncrement; + ULONG uPageSize; + ULONG uMmNumberOfPhysicalPages; + ULONG uMmLowestPhysicalPage; + ULONG uMmHighestPhysicalPage; + ULONG uAllocationGranularity; + PVOID pLowestUserAddress; + PVOID pMmHighestUserAddress; + ULONG uKeActiveProcessors; + BYTE bKeNumberProcessors; + BYTE bUnknown2; + WORD wUnknown3; +} SYSTEM_BASIC_INFORMATION; + +typedef struct +{ + LARGE_INTEGER liIdleTime; + DWORD dwSpare[80]; +} SYSTEM_PERFORMANCE_INFORMATION; + +typedef struct +{ + LARGE_INTEGER liKeBootTime; + LARGE_INTEGER liKeSystemTime; + LARGE_INTEGER liExpTimeZoneBias; + ULONG uCurrentTimeZoneId; + DWORD dwReserved; +} SYSTEM_TIME_INFORMATION; + +typedef LONG (WINAPI *PROCNTQSI)(UINT,PVOID,ULONG,PULONG); + +static PROCNTQSI NtQuerySystemInformation; + +float GetCPUUsage() +{ + SYSTEM_PERFORMANCE_INFORMATION SysPerfInfo; + SYSTEM_TIME_INFORMATION SysTimeInfo; + SYSTEM_BASIC_INFORMATION SysBaseInfo; + double dbIdleTime; + double dbSystemTime; + LONG status; + static LARGE_INTEGER liOldIdleTime = {0,0}; + static LARGE_INTEGER liOldSystemTime = {0,0}; + + if ( !NtQuerySystemInformation) + { + NtQuerySystemInformation = (PROCNTQSI)GetProcAddress( GetModuleHandle("ntdll"), "NtQuerySystemInformation" ); + + if ( !NtQuerySystemInformation ) + return(0); + } + + // get number of processors in the system + status = NtQuerySystemInformation( SystemBasicInformation,&SysBaseInfo,sizeof(SysBaseInfo),NULL ); + if ( status != NO_ERROR ) + return(0); + + // get new system time + status = NtQuerySystemInformation( SystemTimeInformation,&SysTimeInfo,sizeof(SysTimeInfo),0 ); + if ( status!=NO_ERROR ) + return(0); + + // get new CPU's idle time + status = NtQuerySystemInformation( SystemPerformanceInformation,&SysPerfInfo,sizeof(SysPerfInfo),NULL ); + if ( status != NO_ERROR ) + return(0); + + // if it's a first call - skip it + if ( liOldIdleTime.QuadPart != 0 ) + { + // CurrentValue = NewValue - OldValue + dbIdleTime = Li2Double(SysPerfInfo.liIdleTime) - Li2Double(liOldIdleTime); + dbSystemTime = Li2Double(SysTimeInfo.liKeSystemTime) - Li2Double(liOldSystemTime); + + // CurrentCpuIdle = IdleTime / SystemTime + dbIdleTime = dbIdleTime / dbSystemTime / (double)SysBaseInfo.bKeNumberProcessors; + + // CurrentCpuUsage% = 100 - (CurrentCpuIdle * 100) / NumberOfProcessors + // dbIdleTime = 100.0 - dbIdleTime * 100.0 / (double)SysBaseInfo.bKeNumberProcessors + 0.5; + } + else + { + dbIdleTime = 1.0f; + } + + // store new CPU's idle and system time + liOldIdleTime = SysPerfInfo.liIdleTime; + liOldSystemTime = SysTimeInfo.liKeSystemTime; + + return (float)(1.0f - dbIdleTime); +} + +#endif // WIN32 + +#ifdef POSIX +#include + +float GetCPUUsage() +{ + double loadavg[3]; + + getloadavg( loadavg, 3 ); + return loadavg[0]; +} +#endif //POSIX diff --git a/tier0/cpumonitoring.cpp b/tier0/cpumonitoring.cpp new file mode 100644 index 0000000..0c9bb47 --- /dev/null +++ b/tier0/cpumonitoring.cpp @@ -0,0 +1,398 @@ +//============ Copyright (c) Valve Corporation, All rights reserved. ============ +// +// A non-trivial number of Valve customers hit performance problems because their CPUs overheat +// and are thermally throttled. While thermal throttling is better than melting it is still a +// hardware flaw and it leads to a bad user experience. In some cases the CPU frequency drops +// (constantly or occasionally) by 50-75%, leading to equal or greater framerate drops. +// +// This is equivalent to a car that goes into limp-home mode to let it continue running after the +// radiator fails -- it's better than destroying the engine, but clearly it needs to be fixed. +// +// When CPU monitoring is enabled a bunch of background threads are created that wake up at +// the set frequency, spin in a loop to measure the actual usable CPU frequency, then sleep again. +// A delay loop is used to measure the frequency because this is portable (it works for Intel +// and AMD and handles both frequency throttling and duty-cycle reductions) and it doesn't +// require administrator privileges. This technique has been used in VTrace for a while. +// +// This code doesn't use normal worker threads because of the special purpose nature of this +// work. The threads are started on demand and are never terminated, in order to simplify +// the code. +// +//=============================================================================== + +#include "pch_tier0.h" +#include "tier0/cpumonitoring.h" + +#ifdef PLATFORM_WINDOWS_PC32 +#include "tier0/threadtools.h" +#define NOMINMAX +#undef min +#undef max +#include +#include "PowrProf.h" +#include +#pragma comment(lib, "PowrProf.lib") + +// This lock protects s_results and s_nDelayMilliseconds. +static CThreadMutex s_lock; +static CPUFrequencyResults s_results; +static unsigned s_nDelayMilliseconds; +// Has monitoring been enabled? If not measurements may still continue +// if kDelayMillisecondsWhenDisabled is non-zero. +static bool s_fEnabled = false; + +// This is the delay between measurements when measurements are 'disabled'. If it +// is zero then the measurements are truly disabled. +const unsigned kDelayMillisecondsWhenDisabled = 0; //5000; +// Delay before first measurement +const unsigned kFirstInterval = 500; +const unsigned kPostMeasureInterval = 5; +const unsigned kMinimumDelay = 300; + +const int nMaxCPUs = 32; + +// This loop spins spinCount times and should take about 50 times spinCount +// cycles to execute. This should be true on any reasonable modern processor +// since the latency of integer add is almost always one cycle. +// The Xbox 360 and PS3 CPUs are the one known exception but this code will +// never run on them. +static void SpinALot( int spinCount ) +{ + __asm + { + mov ecx, spinCount +start: + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + add eax, eax + + sub ecx,1 + jne start + } +} + +static LARGE_INTEGER s_QPCfrequency; +static LARGE_INTEGER s_QPCbase; + +static void InitializeGetTime() +{ + QueryPerformanceFrequency( &s_QPCfrequency ); + QueryPerformanceCounter( &s_QPCbase ); +} + +static double GetTime() +{ + LARGE_INTEGER value; + QueryPerformanceCounter( &value ); + + // Subtracting off the base time gives us a zero point at application start up and + // gives us more precision. + return ( value.QuadPart - s_QPCbase.QuadPart ) / double( s_QPCfrequency.QuadPart ); +} + +static float GetFrequency() +{ + double start = GetTime(); + // This should cause a delay of 500,000 cycles (50 * spinCount) which should be a + // fraction of a millisecond on any reasonable processor, thus ensuring that the + // sampling interrupt will not be hit too frequently. + SpinALot( 10000 ); + double elapsed = GetTime() - start; + double frequency = ( 500000 / elapsed ) / 1e9; + return (float)frequency; +} + +// This semaphore is used to release all of the measurement threads simultaneously. +static HANDLE g_releaseSemaphore; +// This semaphore is used to wait for all of the measurement threads to complete. +static HANDLE g_workCompleteSemaphore; +static DWORD g_numCPUs; + +// This function measures the CPU frequency by doing repeated integer adds. +// It measures it multiple times and records the highest frequency -- the +// assumption is that any given test might be slowed by interrupts or +// context switches so the fastest run should indicate the true performance. +static float GetSampledFrequency( int iterations ) +{ + float maxFrequency = 0.0; + for ( int i = 0; i < iterations; ++i ) + { + float frequency = GetFrequency(); + if ( frequency > maxFrequency ) + maxFrequency = frequency; + } + + return maxFrequency; +} + +// The measured frequency of all of the threads +static float s_frequencies[ nMaxCPUs ]; + +// Measurement thread, designed to be one per core. +static DWORD WINAPI MeasureThread( LPVOID vThreadNum ) +{ + ThreadSetDebugName( "CPUMonitoringMeasureThread" ); + int threadNum = (int)vThreadNum; + for ( ; ; ) + { + // Wait until the MCP says it's time to wake up and measure CPU speed + WaitForSingleObject( g_releaseSemaphore, INFINITE ); + + // Seven seems like a good number of times to measure the frequency -- it makes + // it likely that a couple of the tests will not hit any interrupts. + float frequency = GetSampledFrequency( 7 ); + s_frequencies[ threadNum ] = frequency; + + // Tell the heartbeat thread that one thread has completed. + ReleaseSemaphore( g_workCompleteSemaphore, 1, NULL ); + } + + // This will never be hit. + return 0; +} + +/* +Note that this structure definition was accidentally omitted from WinNT.h. This error will be corrected in the future. In the meantime, to compile your application, include the structure definition contained in this topic in your source code. +*/ +typedef struct _PROCESSOR_POWER_INFORMATION { + ULONG Number; + ULONG MaxMhz; + ULONG CurrentMhz; + ULONG MhzLimit; + ULONG MaxIdleState; + ULONG CurrentIdleState; +} PROCESSOR_POWER_INFORMATION, *PPROCESSOR_POWER_INFORMATION; + +// Master control thread to periodically wake the measurement threads. +static DWORD WINAPI HeartbeatThread( LPVOID ) +{ + ThreadSetDebugName( "CPUMonitoringHeartbeatThread" ); + // Arbitrary/hacky time to wait for results to become available. + Sleep( kFirstInterval ); + for ( ; ; ) + { + unsigned delay; + { + // Read and write all the state that is shared with the main thread while holding the lock. + AUTO_LOCK( s_lock ); + delay = s_nDelayMilliseconds; + } + + // If monitoring is currently enabled then do the work. + if ( delay ) + { + // First ask Windows what the processor speed is -- this *might* reflect + // some types of thermal throttling, but doesn't seem to. + PROCESSOR_POWER_INFORMATION processorInfo[ nMaxCPUs ] = {}; + CallNtPowerInformation( ProcessorInformation, NULL, 0, &processorInfo, sizeof(processorInfo[0]) * g_numCPUs ); + + ULONG MaxMHz = processorInfo[ 0 ].MaxMhz; + ULONG LimitMHz = processorInfo[ 0 ].MhzLimit; + ULONG MinCurrentMHz = processorInfo[ 0 ].CurrentMhz; + ULONG MaxCurrentMHz = processorInfo[ 0 ].CurrentMhz; + for ( DWORD i = 0; i < g_numCPUs; ++i ) + { + MinCurrentMHz = std::min( MinCurrentMHz, processorInfo[ i ].CurrentMhz ); + MaxCurrentMHz = std::max( MaxCurrentMHz, processorInfo[ i ].CurrentMhz ); + MaxMHz = std::max( MaxMHz, processorInfo[ i ].MaxMhz ); + LimitMHz = std::max( LimitMHz, processorInfo[ i ].MhzLimit ); + } + + // This will wake up all of the worker threads. It is possible that some of the + // threads will take a long time to wake up in which case the same thread might + // wake up multiple times but this should be harmless. + ReleaseSemaphore( g_releaseSemaphore, g_numCPUs, NULL ); + + // Wait until all of the measurement threads should have run. + // This is just to avoid having the heartbeat thread fighting for cycles + // but isn't strictly necessary. + Sleep( kPostMeasureInterval ); + + // Wait for all of the worker threads to finish. + for ( DWORD i = 0; i < g_numCPUs; ++i ) + { + WaitForSingleObject( g_workCompleteSemaphore, INFINITE ); + } + + // Find the minimum and maximum measured frequencies. + float minActualFreq = s_frequencies[ 0 ]; + float maxActualFreq = s_frequencies[ 0 ]; + for ( DWORD i = 1; i < g_numCPUs; ++i ) + { + minActualFreq = std::min( minActualFreq, s_frequencies[ i ] ); + maxActualFreq = std::max( maxActualFreq, s_frequencies[ i ] ); + } + + { + // Read and write all the state that is shared with the main thread while holding the lock. + AUTO_LOCK( s_lock ); + float freqPercentage = maxActualFreq / (MaxCurrentMHz * 1e-5f); + const float kFudgeFactor = 1.03f; // Make results match reality better + s_results.m_timeStamp = Plat_FloatTime(); + s_results.m_GHz = maxActualFreq * kFudgeFactor; + s_results.m_percentage = freqPercentage * kFudgeFactor; + + if ( s_results.m_lowestPercentage == 0 || s_results.m_percentage < s_results.m_lowestPercentage ) + s_results.m_lowestPercentage = s_results.m_percentage; + // delay may get set to zero at this point + delay = s_nDelayMilliseconds; + } + + Sleep( delay ); + } + else + { + // If there is nothing to do then just sleep for a bit. + Sleep( kMinimumDelay ); + } + } + + // This will never be hit. + return 0; +} + +PLATFORM_INTERFACE CPUFrequencyResults GetCPUFrequencyResults( bool fGetDisabledResults ) +{ + AUTO_LOCK( s_lock ); + if ( s_fEnabled || fGetDisabledResults ) + { + // Return actual results. + return s_results; + } + else + { + // Return zero initialized struct. + return CPUFrequencyResults(); + } +} + +PLATFORM_INTERFACE void SetCPUMonitoringInterval( unsigned nDelayMilliseconds ) +{ + static bool s_initialized = false; + + // Clamp the delay to a minimum value to save users from running the + // measurements too frequently. + if ( nDelayMilliseconds && nDelayMilliseconds <= kMinimumDelay ) + nDelayMilliseconds = kMinimumDelay; + + // If not yet initialized then do one-time thread initialization + if ( !s_initialized ) + { + s_initialized = true; + + InitializeGetTime(); + + g_releaseSemaphore = CreateSemaphore( NULL, 0, 1000, NULL ); + if ( !g_releaseSemaphore ) + return; + g_workCompleteSemaphore = CreateSemaphore( NULL, 0, 1000, NULL ); + if ( !g_workCompleteSemaphore ) + return; + + SYSTEM_INFO systemInfo; + GetSystemInfo( &systemInfo ); + + g_numCPUs = systemInfo.dwNumberOfProcessors; + if ( g_numCPUs > nMaxCPUs ) + g_numCPUs = nMaxCPUs; + + // Create n threads, affinitize them, and set them to high priority. This will (mostly) + // ensure that they will run promptly on a specific CPU. + for ( DWORD i = 0; i < g_numCPUs; ++i ) + { + HANDLE thread = CreateThread( NULL, 0x10000, MeasureThread, (void*)i, 0, NULL ); + SetThreadAffinityMask( thread, 1u << i ); + SetThreadPriority( thread, THREAD_PRIORITY_HIGHEST ); + } + + // Create the thread which tells the measurement threads to wake up periodically + CreateThread( NULL, 0x10000, HeartbeatThread, NULL, 0, NULL ); + } + + AUTO_LOCK( s_lock ); + if ( nDelayMilliseconds && s_nDelayMilliseconds == 0 ) + { + // If we are enabling/re-enabling then reset the stats. + memset( &s_results, 0, sizeof(s_results) ); + } + // Set the specified delay time or 5,000 if it is disabled. + s_nDelayMilliseconds = nDelayMilliseconds ? nDelayMilliseconds : kDelayMillisecondsWhenDisabled; + s_fEnabled = nDelayMilliseconds != 0; +} + +class CPUMonitoringStarter +{ +public: + CPUMonitoringStarter() + { + // Start up the disabled CPU monitoring at low frequency. + if ( kDelayMillisecondsWhenDisabled ) + SetCPUMonitoringInterval( 0 ); + } +} s_CPUMonitoringStarter; + +#else +PLATFORM_INTERFACE CPUFrequencyResults GetCPUFrequencyResults(bool) +{ + // Return zero initialized results which means no data available. + CPUFrequencyResults results = {}; + return results; +} + +PLATFORM_INTERFACE void SetCPUMonitoringInterval( unsigned nDelayMilliseconds ) +{ + NOTE_UNUSED( nDelayMilliseconds ); +} + +#endif diff --git a/tier0/dbg.cpp b/tier0/dbg.cpp new file mode 100644 index 0000000..ce01801 --- /dev/null +++ b/tier0/dbg.cpp @@ -0,0 +1,948 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include "pch_tier0.h" +#include "tier0/minidump.h" + +#if defined( _WIN32 ) && !defined( _X360 ) +#include "tier0/valve_off.h" +#define WIN_32_LEAN_AND_MEAN +#include // Currently needed for IsBadReadPtr and IsBadWritePtr +#pragma comment(lib,"user32.lib") // For MessageBox +#endif + +#include +#include +#include +#include +#include +#include +#include "Color.h" +#include "tier0/dbg.h" +#include "tier0/threadtools.h" +#include "tier0/icommandline.h" +#include +#if defined( _X360 ) +#include "xbox/xbox_console.h" +#endif + +#include "tier0/etwprof.h" + +#ifndef STEAM +#define PvRealloc realloc +#define PvAlloc malloc +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// internal structures +//----------------------------------------------------------------------------- +enum +{ + MAX_GROUP_NAME_LENGTH = 48 +}; + +struct SpewGroup_t +{ + tchar m_GroupName[MAX_GROUP_NAME_LENGTH]; + int m_Level; +}; + +// Skip forward past the directory +static const char *SkipToFname( const tchar* pFile ) +{ + if ( pFile == NULL ) + return "unknown"; + const tchar* pSlash = _tcsrchr( pFile, '\\' ); + const tchar* pSlash2 = _tcsrchr( pFile, '/' ); + if (pSlash < pSlash2) pSlash = pSlash2; + return pSlash ? pSlash + 1: pFile; +} + + +//----------------------------------------------------------------------------- +DBG_INTERFACE SpewRetval_t DefaultSpewFunc( SpewType_t type, const tchar *pMsg ) +{ +#ifdef _X360 + if ( XBX_IsConsoleConnected() ) + { + // send to console + XBX_DebugString( XMAKECOLOR( 0,0,0 ), pMsg ); + } + else +#endif + { + _tprintf( _T("%s"), pMsg ); +#ifdef _WIN32 + Plat_DebugString( pMsg ); +#endif + } + if ( type == SPEW_ASSERT ) + { +#ifndef WIN32 + // Non-win32 + bool bRaiseOnAssert = getenv( "RAISE_ON_ASSERT" ) || !!CommandLine()->FindParm( "-raiseonassert" ); +#elif defined( _DEBUG ) + // Win32 debug + bool bRaiseOnAssert = true; +#else + // Win32 release + bool bRaiseOnAssert = !!CommandLine()->FindParm( "-raiseonassert" ); +#endif + + return bRaiseOnAssert ? SPEW_DEBUGGER : SPEW_CONTINUE; + } + else if ( type == SPEW_ERROR ) + return SPEW_ABORT; + else + return SPEW_CONTINUE; +} + +//----------------------------------------------------------------------------- +DBG_INTERFACE SpewRetval_t DefaultSpewFuncAbortOnAsserts( SpewType_t type, const tchar *pMsg ) +{ + SpewRetval_t r = DefaultSpewFunc( type, pMsg ); + if ( type == SPEW_ASSERT ) + r = SPEW_ABORT; + return r; +} + + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +static SpewOutputFunc_t s_SpewOutputFunc = DefaultSpewFunc; + +static AssertFailedNotifyFunc_t s_AssertFailedNotifyFunc = NULL; + +static const tchar* s_pFileName; +static int s_Line; +static SpewType_t s_SpewType; + +static SpewGroup_t* s_pSpewGroups = 0; +static int s_GroupCount = 0; +static int s_DefaultLevel = 0; +#if !defined( _X360 ) +static Color s_DefaultOutputColor( 255, 255, 255, 255 ); +#else +static Color s_DefaultOutputColor( 0, 0, 0, 255 ); +#endif + +// Only useable from within a spew function +struct SpewInfo_t +{ + const Color* m_pSpewOutputColor; + const tchar* m_pSpewOutputGroup; + int m_nSpewOutputLevel; +}; + +CThreadLocalPtr g_pSpewInfo; + + +// Standard groups +static const tchar* s_pDeveloper = _T("developer"); +static const tchar* s_pConsole = _T("console"); +static const tchar* s_pNetwork = _T("network"); + +enum StandardSpewGroup_t +{ + GROUP_DEVELOPER = 0, + GROUP_CONSOLE, + GROUP_NETWORK, + + GROUP_COUNT, +}; + +static int s_pGroupIndices[GROUP_COUNT] = { -1, -1, -1 }; +static const char *s_pGroupNames[GROUP_COUNT] = { s_pDeveloper, s_pConsole, s_pNetwork }; + + +//----------------------------------------------------------------------------- +// Spew output management. +//----------------------------------------------------------------------------- +void SpewOutputFunc( SpewOutputFunc_t func ) +{ + s_SpewOutputFunc = func ? func : DefaultSpewFunc; +} + +SpewOutputFunc_t GetSpewOutputFunc( void ) +{ + if( s_SpewOutputFunc ) + return s_SpewOutputFunc; + return DefaultSpewFunc; +} + +void _ExitOnFatalAssert( const tchar* pFile, int line ) +{ + _SpewMessage( _T("Fatal assert failed: %s, line %d. Application exiting.\n"), pFile, line ); + + // only write out minidumps if we're not in the debugger + if ( !Plat_IsInDebugSession() ) + { + char rgchSuffix[512]; + _snprintf( rgchSuffix, sizeof(rgchSuffix), "fatalassert_%s_%d", SkipToFname( pFile ), line ); + WriteMiniDump( rgchSuffix ); + } + + DevMsg( 1, _T("_ExitOnFatalAssert\n") ); + exit( EXIT_FAILURE ); +} + + +//----------------------------------------------------------------------------- +// Templates to assist in validating pointers: +//----------------------------------------------------------------------------- + + +DBG_INTERFACE void _AssertValidReadPtr( void* ptr, int count/* = 1*/ ) +{ + Assert( !count || ptr ); +} + +DBG_INTERFACE void _AssertValidWritePtr( void* ptr, int count/* = 1*/ ) +{ + Assert( !count || ptr ); +} + +DBG_INTERFACE void _AssertValidReadWritePtr( void* ptr, int count/* = 1*/ ) +{ + Assert( !count || ptr ); +} + +#undef AssertValidStringPtr +DBG_INTERFACE void AssertValidStringPtr( const tchar* ptr, int maxchar/* = 0xFFFFFF */ ) +{ + Assert( ptr ); +} + +//----------------------------------------------------------------------------- +// Should be called only inside a SpewOutputFunc_t, returns groupname, level, color +//----------------------------------------------------------------------------- +const tchar* GetSpewOutputGroup( void ) +{ + SpewInfo_t *pSpewInfo = g_pSpewInfo; + assert( pSpewInfo ); + if ( pSpewInfo ) + return pSpewInfo->m_pSpewOutputGroup; + return NULL; +} + +int GetSpewOutputLevel( void ) +{ + SpewInfo_t *pSpewInfo = g_pSpewInfo; + assert( pSpewInfo ); + if ( pSpewInfo ) + return pSpewInfo->m_nSpewOutputLevel; + return -1; +} + +const Color* GetSpewOutputColor( void ) +{ + SpewInfo_t *pSpewInfo = g_pSpewInfo; + assert( pSpewInfo ); + if ( pSpewInfo ) + return pSpewInfo->m_pSpewOutputColor; + return &s_DefaultOutputColor; +} + + +//----------------------------------------------------------------------------- +// Spew functions +//----------------------------------------------------------------------------- +DBG_INTERFACE void _SpewInfo( SpewType_t type, const tchar* pFile, int line ) +{ + // Only grab the file name. Ignore the path. + s_pFileName = SkipToFname( pFile ); + s_Line = line; + s_SpewType = type; +} + + +static SpewRetval_t _SpewMessage( SpewType_t spewType, const char *pGroupName, int nLevel, const Color *pColor, const tchar* pMsgFormat, va_list args ) +{ + tchar pTempBuffer[5020]; + + assert( _tcslen( pMsgFormat ) < sizeof( pTempBuffer) ); // check that we won't artifically truncate the string + + /* Printf the file and line for warning + assert only... */ + int len = 0; + if ( spewType == SPEW_ASSERT ) + { + len = _sntprintf( pTempBuffer, sizeof( pTempBuffer ) - 1, _T("%s (%d) : "), s_pFileName, s_Line ); + } + + if ( len == -1 ) + return SPEW_ABORT; + + /* Create the message.... */ + int val= _vsntprintf( &pTempBuffer[len], sizeof( pTempBuffer ) - len - 1, pMsgFormat, args ); + if ( val == -1 ) + return SPEW_ABORT; + + len += val; + assert( len * sizeof(*pMsgFormat) < sizeof(pTempBuffer) ); /* use normal assert here; to avoid recursion. */ + + // Add \n for warning and assert + if ( spewType == SPEW_ASSERT ) + { + len += _stprintf( &pTempBuffer[len], _T("\n") ); + } + + assert( len < sizeof(pTempBuffer)/sizeof(pTempBuffer[0]) - 1 ); /* use normal assert here; to avoid recursion. */ + assert( s_SpewOutputFunc ); + + /* direct it to the appropriate target(s) */ + SpewRetval_t ret; + assert( g_pSpewInfo == NULL ); + SpewInfo_t spewInfo = + { + pColor, + pGroupName, + nLevel + }; + + g_pSpewInfo = &spewInfo; + ret = s_SpewOutputFunc( spewType, pTempBuffer ); + g_pSpewInfo = (int)NULL; + + switch (ret) + { +// Asserts put the break into the macro so it occurs in the right place + case SPEW_DEBUGGER: + if ( spewType != SPEW_ASSERT ) + { + DebuggerBreak(); + } + break; + + case SPEW_ABORT: + { +// MessageBox(NULL,"Error in _SpewMessage","Error",MB_OK); +// ConMsg( _T("Exiting on SPEW_ABORT\n") ); + exit(1); + } + } + + return ret; +} + +#include "tier0/valve_off.h" + +FORCEINLINE SpewRetval_t _SpewMessage( SpewType_t spewType, const tchar* pMsgFormat, va_list args ) +{ + return _SpewMessage( spewType, "", 0, &s_DefaultOutputColor, pMsgFormat, args ); +} + + +//----------------------------------------------------------------------------- +// Find a group, return true if found, false if not. Return in ind the +// index of the found group, or the index of the group right before where the +// group should be inserted into the list to maintain sorted order. +//----------------------------------------------------------------------------- +bool FindSpewGroup( const tchar* pGroupName, int* pInd ) +{ + int s = 0; + if (s_GroupCount) + { + int e = (int)(s_GroupCount - 1); + while ( s <= e ) + { + int m = (s+e) >> 1; + int cmp = _tcsicmp( pGroupName, s_pSpewGroups[m].m_GroupName ); + if ( !cmp ) + { + *pInd = m; + return true; + } + if ( cmp < 0 ) + e = m - 1; + else + s = m + 1; + } + } + *pInd = s; + return false; +} + +//----------------------------------------------------------------------------- +// True if -hushasserts was passed on command line. +//----------------------------------------------------------------------------- +bool HushAsserts() +{ +#ifdef DBGFLAG_ASSERT + static bool s_bHushAsserts = !!CommandLine()->FindParm( "-hushasserts" ); + return s_bHushAsserts; +#else + return true; +#endif +} + +//----------------------------------------------------------------------------- +// Tests to see if a particular spew is active +//----------------------------------------------------------------------------- +bool IsSpewActive( const tchar* pGroupName, int level ) +{ + // If we don't find the spew group, use the default level. + int ind; + if ( FindSpewGroup( pGroupName, &ind ) ) + return s_pSpewGroups[ind].m_Level >= level; + else + return s_DefaultLevel >= level; +} + +inline bool IsSpewActive( StandardSpewGroup_t group, int level ) +{ + // If we don't find the spew group, use the default level. + if ( s_pGroupIndices[group] >= 0 ) + return s_pSpewGroups[ s_pGroupIndices[group] ].m_Level >= level; + return s_DefaultLevel >= level; +} + +SpewRetval_t _SpewMessage( const tchar* pMsgFormat, ... ) +{ + va_list args; + va_start( args, pMsgFormat ); + SpewRetval_t ret = _SpewMessage( s_SpewType, pMsgFormat, args ); + va_end(args); + return ret; +} + +SpewRetval_t _DSpewMessage( const tchar *pGroupName, int level, const tchar* pMsgFormat, ... ) +{ + if( !IsSpewActive( pGroupName, level ) ) + return SPEW_CONTINUE; + + va_list args; + va_start( args, pMsgFormat ); + SpewRetval_t ret = _SpewMessage( s_SpewType, pGroupName, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); + return ret; +} + +DBG_INTERFACE SpewRetval_t ColorSpewMessage( SpewType_t type, const Color *pColor, const tchar* pMsgFormat, ... ) +{ + va_list args; + va_start( args, pMsgFormat ); + SpewRetval_t ret = _SpewMessage( type, "", 0, pColor, pMsgFormat, args ); + va_end(args); + return ret; +} + +void Msg( const tchar* pMsgFormat, ... ) +{ + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, pMsgFormat, args ); + va_end(args); +} + +void DMsg( const tchar *pGroupName, int level, const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( pGroupName, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, pGroupName, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void MsgV( PRINTF_FORMAT_STRING const tchar *pMsg, va_list arglist ) +{ + _SpewMessage( SPEW_MESSAGE, pMsg, arglist ); +} + + +void Warning( const tchar *pMsgFormat, ... ) +{ + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_WARNING, pMsgFormat, args ); + va_end(args); +} + +void DWarning( const tchar *pGroupName, int level, const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( pGroupName, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_WARNING, pGroupName, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void WarningV( PRINTF_FORMAT_STRING const tchar *pMsg, va_list arglist ) +{ + _SpewMessage( SPEW_WARNING, pMsg, arglist ); +} + + +void Log( const tchar *pMsgFormat, ... ) +{ + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_LOG, pMsgFormat, args ); + va_end(args); +} + +void DLog( const tchar *pGroupName, int level, const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( pGroupName, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_LOG, pGroupName, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void LogV( PRINTF_FORMAT_STRING const tchar *pMsg, va_list arglist ) +{ + _SpewMessage( SPEW_LOG, pMsg, arglist ); +} + + +void Error( const tchar *pMsgFormat, ... ) +{ + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_ERROR, pMsgFormat, args ); + va_end(args); +} + +void ErrorV( PRINTF_FORMAT_STRING const tchar *pMsg, va_list arglist ) +{ + _SpewMessage( SPEW_ERROR, pMsg, arglist ); +} + +//----------------------------------------------------------------------------- +// A couple of super-common dynamic spew messages, here for convenience +// These looked at the "developer" group, print if it's level 1 or higher +//----------------------------------------------------------------------------- +void DevMsg( int level, const tchar* pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_DEVELOPER, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, s_pDeveloper, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void DevWarning( int level, const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_DEVELOPER, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_WARNING, s_pDeveloper, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void DevLog( int level, const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_DEVELOPER, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_LOG, s_pDeveloper, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void DevMsg( const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_DEVELOPER, 1 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, s_pDeveloper, 1, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void DevWarning( const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_DEVELOPER, 1 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_WARNING, s_pDeveloper, 1, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void DevLog( const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_DEVELOPER, 1 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_LOG, s_pDeveloper, 1, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + + +//----------------------------------------------------------------------------- +// A couple of super-common dynamic spew messages, here for convenience +// These looked at the "console" group, print if it's level 1 or higher +//----------------------------------------------------------------------------- +void ConColorMsg( int level, const Color& clr, const tchar* pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, s_pConsole, level, &clr, pMsgFormat, args ); + va_end(args); +} + +void ConMsg( int level, const tchar* pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, s_pConsole, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void ConWarning( int level, const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_WARNING, s_pConsole, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void ConLog( int level, const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_LOG, s_pConsole, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void ConColorMsg( const Color& clr, const tchar* pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, 1 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, s_pConsole, 1, &clr, pMsgFormat, args ); + va_end(args); +} + +void ConMsg( const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, 1 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, s_pConsole, 1, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void ConWarning( const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, 1 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_WARNING, s_pConsole, 1, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void ConLog( const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, 1 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_LOG, s_pConsole, 1, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + + +void ConDColorMsg( const Color& clr, const tchar* pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, 2 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, s_pConsole, 2, &clr, pMsgFormat, args ); + va_end(args); +} + +void ConDMsg( const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, 2 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, s_pConsole, 2, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void ConDWarning( const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, 2 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_WARNING, s_pConsole, 2, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void ConDLog( const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_CONSOLE, 2 ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_LOG, s_pConsole, 2, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + + +//----------------------------------------------------------------------------- +// A couple of super-common dynamic spew messages, here for convenience +// These looked at the "network" group, print if it's level 1 or higher +//----------------------------------------------------------------------------- +void NetMsg( int level, const tchar* pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_NETWORK, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_MESSAGE, s_pNetwork, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void NetWarning( int level, const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_NETWORK, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_WARNING, s_pNetwork, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +void NetLog( int level, const tchar *pMsgFormat, ... ) +{ + if( !IsSpewActive( GROUP_NETWORK, level ) ) + return; + + va_list args; + va_start( args, pMsgFormat ); + _SpewMessage( SPEW_LOG, s_pNetwork, level, &s_DefaultOutputColor, pMsgFormat, args ); + va_end(args); +} + +#include "tier0/valve_on.h" + + +//----------------------------------------------------------------------------- +// Sets the priority level for a spew group +//----------------------------------------------------------------------------- +void SpewActivate( const tchar* pGroupName, int level ) +{ + Assert( pGroupName ); + + // check for the default group first... + if ((pGroupName[0] == '*') && (pGroupName[1] == '\0')) + { + s_DefaultLevel = level; + return; + } + + // Normal case, search in group list using binary search. + // If not found, grow the list of groups and insert it into the + // right place to maintain sorted order. Then set the level. + int ind; + if ( !FindSpewGroup( pGroupName, &ind ) ) + { + // not defined yet, insert an entry. + ++s_GroupCount; + if ( s_pSpewGroups ) + { + s_pSpewGroups = (SpewGroup_t*)PvRealloc( s_pSpewGroups, + s_GroupCount * sizeof(SpewGroup_t) ); + + // shift elements down to preserve order + int numToMove = s_GroupCount - ind - 1; + memmove( &s_pSpewGroups[ind+1], &s_pSpewGroups[ind], + numToMove * sizeof(SpewGroup_t) ); + + // Update standard groups + for ( int i = 0; i < GROUP_COUNT; ++i ) + { + if ( ( ind <= s_pGroupIndices[i] ) && ( s_pGroupIndices[i] >= 0 ) ) + { + ++s_pGroupIndices[i]; + } + } + } + else + { + s_pSpewGroups = (SpewGroup_t*)PvAlloc( s_GroupCount * sizeof(SpewGroup_t) ); + } + + Assert( _tcslen( pGroupName ) < MAX_GROUP_NAME_LENGTH ); + _tcscpy( s_pSpewGroups[ind].m_GroupName, pGroupName ); + + // Update standard groups + for ( int i = 0; i < GROUP_COUNT; ++i ) + { + if ( ( s_pGroupIndices[i] < 0 ) && !_tcsicmp( s_pGroupNames[i], pGroupName ) ) + { + s_pGroupIndices[i] = ind; + break; + } + } + } + s_pSpewGroups[ind].m_Level = level; +} + + +// If we don't have a function from math.h, then it doesn't link certain floating-point +// functions in and printfs with %f cause runtime errors in the C libraries. +DBG_INTERFACE float CrackSmokingCompiler( float a ) +{ + return (float)fabs( a ); +} + +void* Plat_SimpleLog( const tchar* file, int line ) +{ + FILE* f = _tfopen( _T("simple.log"), _T("at+") ); + _ftprintf( f, _T("%s:%i\n"), file, line ); + fclose( f ); + + return NULL; +} + +#ifdef DBGFLAG_VALIDATE +void ValidateSpew( CValidator &validator ) +{ + validator.Push( _T("Spew globals"), NULL, _T("Global") ); + + validator.ClaimMemory( s_pSpewGroups ); + + validator.Pop( ); +} +#endif // DBGFLAG_VALIDATE + +//----------------------------------------------------------------------------- +// Purpose: For debugging startup times, etc. +// Input : *fmt - +// ... - +//----------------------------------------------------------------------------- +void COM_TimestampedLog( char const *fmt, ... ) +{ + static float s_LastStamp = 0.0; + static bool s_bShouldLog = false; + static bool s_bShouldLogToETW = false; + static bool s_bChecked = false; + static bool s_bFirstWrite = false; + + if ( !s_bChecked ) + { + s_bShouldLog = ( IsX360() || CommandLine()->CheckParm( "-profile" ) ) ? true : false; + s_bShouldLogToETW = ( CommandLine()->CheckParm( "-etwprofile" ) ) ? true : false; + if ( s_bShouldLogToETW ) + { + s_bShouldLog = true; + } + s_bChecked = true; + } + if ( !s_bShouldLog ) + { + return; + } + + char string[1024]; + va_list argptr; + va_start( argptr, fmt ); + _vsnprintf( string, sizeof( string ), fmt, argptr ); + va_end( argptr ); + + float curStamp = Plat_FloatTime(); + +#if defined( _X360 ) + XBX_rTimeStampLog( curStamp, string ); +#endif + + if ( IsPC() ) + { + // If ETW profiling is enabled then do it only. + if ( s_bShouldLogToETW ) + { + ETWMark( string ); + } + else + { + if ( !s_bFirstWrite ) + { + unlink( "timestamped.log" ); + s_bFirstWrite = true; + } + + FILE* fp = fopen( "timestamped.log", "at+" ); + fprintf( fp, "%8.4f / %8.4f: %s\n", curStamp, curStamp - s_LastStamp, string ); + fclose( fp ); + } + } + + s_LastStamp = curStamp; +} + +//----------------------------------------------------------------------------- +// Sets an assert failed notify handler +//----------------------------------------------------------------------------- +void SetAssertFailedNotifyFunc( AssertFailedNotifyFunc_t func ) +{ + s_AssertFailedNotifyFunc = func; +} + + +//----------------------------------------------------------------------------- +// Calls the assert failed notify handler if one has been set +//----------------------------------------------------------------------------- +void CallAssertFailedNotifyFunc( const char *pchFile, int nLine, const char *pchMessage ) +{ + if ( s_AssertFailedNotifyFunc ) + s_AssertFailedNotifyFunc( pchFile, nLine, pchMessage ); +} + + diff --git a/tier0/dynfunction.cpp b/tier0/dynfunction.cpp new file mode 100644 index 0000000..e05dcb3 --- /dev/null +++ b/tier0/dynfunction.cpp @@ -0,0 +1,148 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Shared library loading and symbol lookup. +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" +#include "tier0/dynfunction.h" + +#if defined(WIN32) +typedef HMODULE LibraryHandle; +#define LoadLibraryHandle(libname) LoadLibrary(libname) +#define CloseLibraryHandle(handle) FreeLibrary(handle) +#define LookupInLibraryHandle(handle, fn) GetProcAddress(handle, fn) +#elif defined(POSIX) +#include +typedef void *LibraryHandle; +#define LoadLibraryHandle(libname) dlopen(libname, RTLD_NOW) +#define CloseLibraryHandle(handle) dlclose(handle) +#define LookupInLibraryHandle(handle, fn) dlsym(handle, fn) +#else +#error Please define your platform. +#endif + +#if 1 +static inline void dbgdynfn(const char *fmt, ...) {} +#else +#define dbgdynfn printf +#endif + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +class CSharedLibraryCache +{ +public: + static CSharedLibraryCache &GetCache() + { + static CSharedLibraryCache Singleton; + return Singleton; + } + + struct CSharedLibraryItem + { + CSharedLibraryItem(LibraryHandle handle, const char *name) + { + m_handle = handle; + m_name = new char[strlen(name) + 1]; + m_next = NULL; + strcpy(m_name, name); + } + + ~CSharedLibraryItem() + { + dbgdynfn("CDynamicFunction: Closing library '%s' (%p)\n", m_name, (void *) m_handle); + CloseLibraryHandle(m_handle); + delete[] m_name; + delete m_next; + } + + char *m_name; + CSharedLibraryItem *m_next; + LibraryHandle m_handle; + }; + + CSharedLibraryCache() : m_pList(NULL) {} + ~CSharedLibraryCache() { CloseAllLibraries(); } + + LibraryHandle GetHandle(const char *name) + { + CSharedLibraryItem *item = GetCacheItem(name); + if (item == NULL) + { + LibraryHandle lib = LoadLibraryHandle(name); + dbgdynfn("CDynamicFunction: Loading library '%s' (%p)\n", name, (void *) lib); + if (lib == NULL) + return NULL; + + item = new CSharedLibraryItem(lib, name); + item->m_next = m_pList; + m_pList = item; + } + return item->m_handle; + } + + void CloseLibrary(const char *name) + { + CSharedLibraryItem *item = GetCacheItem(name); + if (item) + { + assert(item == m_pList); + m_pList = item->m_next; + item->m_next = NULL; + delete item; + } + } + + void CloseAllLibraries() + { + delete m_pList; + } + +private: + CSharedLibraryItem *GetCacheItem(const char *name) + { + CSharedLibraryItem *prev = NULL; + CSharedLibraryItem *item = m_pList; + while (item) + { + if (strcmp(item->m_name, name) == 0) + { + // move this item to the front of the list, since there will + // probably be a big pile of these lookups in a row + // and then none ever again. + if (prev != NULL) + { + prev->m_next = item->m_next; + item->m_next = m_pList; + m_pList = item; + } + return item; + } + + prev = item; + item = item->m_next; + } + return NULL; // not found. + } + + CSharedLibraryItem *m_pList; +}; + +void *VoidFnPtrLookup_Tier0(const char *libname, const char *fn, void *fallback) +{ + LibraryHandle lib = CSharedLibraryCache::GetCache().GetHandle(libname); + void *retval = NULL; + if (lib != NULL) + { + retval = LookupInLibraryHandle(lib, fn); + dbgdynfn("CDynamicFunction: Lookup of '%s' in '%s': %p\n", fn, libname, retval); + } + + if (retval == NULL) + retval = fallback; + return retval; +} + diff --git a/tier0/etwprof.cpp b/tier0/etwprof.cpp new file mode 100644 index 0000000..6f8a54c --- /dev/null +++ b/tier0/etwprof.cpp @@ -0,0 +1,406 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// ETW (Event Tracing for Windows) profiling helpers. +// This allows easy insertion of Generic Event markers into ETW/xperf tracing +// which then aids in analyzing the traces and finding performance problems. +// +//=============================================================================== + +#include "pch_tier0.h" +#include "tier0/etwprof.h" + +#ifdef ETW_MARKS_ENABLED + +#include + +// After building the DLL if it has never been registered on this machine or +// if the providers have changed you need to go: +// xcopy /y %vgame%\bin\tier0.dll %temp% +// wevtutil um %vgame%\..\src\tier0\ValveETWProvider.man +// wevtutil im %vgame%\..\src\tier0\ValveETWProvider.man + +#define WIN32_LEAN_AND_MEAN +#include +// These are defined in evntrace.h but you need a Vista+ Windows +// SDK to have them available, so I define them here. +#define EVENT_CONTROL_CODE_DISABLE_PROVIDER 0 +#define EVENT_CONTROL_CODE_ENABLE_PROVIDER 1 +#define EVENT_CONTROL_CODE_CAPTURE_STATE 2 + +// EVNTAPI is used in evntprov.h which is included by ValveETWProviderEvents.h +// We define EVNTAPI without the DECLSPEC_IMPORT specifier so that +// we can implement these functions locally instead of using the import library, +// and can therefore still run on Windows XP. +#define EVNTAPI __stdcall +// Include the event register/write/unregister macros compiled from the manifest file. +// Note that this includes evntprov.h which requires a Vista+ Windows SDK +// which we don't currently have, so evntprov.h is checked in. +#include "ValveETWProviderEvents.h" + +// Typedefs for use with GetProcAddress +typedef ULONG (__stdcall *tEventRegister)( LPCGUID ProviderId, PENABLECALLBACK EnableCallback, PVOID CallbackContext, PREGHANDLE RegHandle); +typedef ULONG (__stdcall *tEventWrite)( REGHANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor, ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData); +typedef ULONG (__stdcall *tEventUnregister)( REGHANDLE RegHandle ); + +// Helper class to dynamically load Advapi32.dll, find the ETW functions, +// register the providers if possible, and get the performance counter frequency. +class CETWRegister +{ +public: + CETWRegister() + { + QueryPerformanceFrequency( &m_frequency ); + + // Find Advapi32.dll. This should always succeed. + HMODULE pAdvapiDLL = LoadLibraryW( L"Advapi32.dll" ); + if ( pAdvapiDLL ) + { + // Try to find the ETW functions. This will fail on XP. + m_pEventRegister = ( tEventRegister )GetProcAddress( pAdvapiDLL, "EventRegister" ); + m_pEventWrite = ( tEventWrite )GetProcAddress( pAdvapiDLL, "EventWrite" ); + m_pEventUnregister = ( tEventUnregister )GetProcAddress( pAdvapiDLL, "EventUnregister" ); + + // Register two ETW providers. If registration fails then the event logging calls will fail. + // On XP these calls will do nothing. + // On Vista and above, if these providers have been enabled by xperf or logman then + // the VALVE_FRAMERATE_Context and VALVE_MAIN_Context globals will be modified + // like this: + // MatchAnyKeyword: 0xffffffffffffffff + // IsEnabled: 1 + // Level: 255 + // In other words, fully enabled. + + EventRegisterValve_FrameRate(); + EventRegisterValve_ServerFrameRate(); + EventRegisterValve_Main(); + EventRegisterValve_Input(); + EventRegisterValve_Network(); + + // Emit the thread ID for the main thread. This also indicates that + // the main provider is initialized. + EventWriteThread_ID( GetCurrentThreadId(), "Main thread" ); + // Emit an input system event so we know that it is active. + EventWriteKey_down( "Valve input provider initialized.", 0, 0 ); + } + } + ~CETWRegister() + { + // Unregister our providers. + EventUnregisterValve_Network(); + EventUnregisterValve_Input(); + EventUnregisterValve_Main(); + EventUnregisterValve_ServerFrameRate(); + EventUnregisterValve_FrameRate(); + } + + tEventRegister m_pEventRegister; + tEventWrite m_pEventWrite; + tEventUnregister m_pEventUnregister; + + // QPC frequency + LARGE_INTEGER m_frequency; + +} g_ETWRegister; + +// Redirector function for EventRegister. Called by macros in ValveETWProviderEvents.h +ULONG EVNTAPI EventRegister( LPCGUID ProviderId, PENABLECALLBACK EnableCallback, PVOID CallbackContext, PREGHANDLE RegHandle ) +{ + if ( g_ETWRegister.m_pEventRegister ) + return g_ETWRegister.m_pEventRegister( ProviderId, EnableCallback, CallbackContext, RegHandle ); + + // RegHandle is an _Out_ parameter and must always be initialized. + *RegHandle = 0; + return 0; +} + +// Redirector function for EventWrite. Called by macros in ValveETWProviderEvents.h +ULONG EVNTAPI EventWrite( REGHANDLE RegHandle, PCEVENT_DESCRIPTOR EventDescriptor, ULONG UserDataCount, PEVENT_DATA_DESCRIPTOR UserData ) +{ + if ( g_ETWRegister.m_pEventWrite ) + return g_ETWRegister.m_pEventWrite( RegHandle, EventDescriptor, UserDataCount, UserData ); + return 0; +} + +// Redirector function for EventUnregister. Called by macros in ValveETWProviderEvents.h +ULONG EVNTAPI EventUnregister( REGHANDLE RegHandle ) +{ + if ( g_ETWRegister.m_pEventUnregister ) + return g_ETWRegister.m_pEventUnregister( RegHandle ); + return 0; +} + +// Call QueryPerformanceCounter +static int64 GetQPCTime() +{ + LARGE_INTEGER time; + + QueryPerformanceCounter( &time ); + return time.QuadPart; +} + +// Convert a QueryPerformanceCounter delta into milliseconds +static float QPCToMS( int64 nDelta ) +{ + // Convert from a QPC delta to seconds. + float flSeconds = ( float )( nDelta / double( g_ETWRegister.m_frequency.QuadPart ) ); + + // Convert from seconds to milliseconds + return flSeconds * 1000; +} + +// Public functions for emitting ETW events. + +int64 ETWMark( const char *pMessage ) +{ + int64 nTime = GetQPCTime(); + EventWriteMark( pMessage ); + return nTime; +} + +void ETWMarkPrintf( const char *pMessage, ... ) +{ + // If we are running on Windows XP or if our providers have not been enabled + // (by xperf or other) then this will be false and we can early out. + // Be sure to check the appropriate context for the event. This is only + // worth checking if there is some cost beyond the EventWrite that we can + // avoid -- the redirectors in this file guarantee that EventWrite is always + // safe to call. + if ( !VALVE_MAIN_Context.IsEnabled ) + { + return; + } + + char buffer[1000]; + va_list args; + va_start( args, pMessage ); + vsprintf_s( buffer, pMessage, args ); + va_end( args ); + + EventWriteMark( buffer ); +} + +void ETWMark1F( const char *pMessage, float data1 ) +{ + EventWriteMark1F( pMessage, data1 ); +} + +void ETWMark2F( const char *pMessage, float data1, float data2 ) +{ + EventWriteMark2F( pMessage, data1, data2 ); +} + +void ETWMark3F( const char *pMessage, float data1, float data2, float data3 ) +{ + EventWriteMark3F( pMessage, data1, data2, data3 ); +} + +void ETWMark4F( const char *pMessage, float data1, float data2, float data3, float data4 ) +{ + EventWriteMark4F( pMessage, data1, data2, data3, data4 ); +} + +void ETWMark1I( const char *pMessage, int data1 ) +{ + EventWriteMark1I( pMessage, data1 ); +} + +void ETWMark2I( const char *pMessage, int data1, int data2 ) +{ + EventWriteMark2I( pMessage, data1, data2 ); +} + +void ETWMark3I( const char *pMessage, int data1, int data2, int data3 ) +{ + EventWriteMark3I( pMessage, data1, data2, data3 ); +} + +void ETWMark4I( const char *pMessage, int data1, int data2, int data3, int data4 ) +{ + EventWriteMark4I( pMessage, data1, data2, data3, data4 ); +} + +void ETWMark1S( const char *pMessage, const char* data1 ) +{ + EventWriteMark1S( pMessage, data1 ); +} + +void ETWMark2S( const char *pMessage, const char* data1, const char* data2 ) +{ + EventWriteMark2S( pMessage, data1, data2 ); +} + +// Track the depth of ETW Begin/End pairs. This needs to be per-thread +// if we start emitting marks on multiple threads. Using __declspec(thread) +// has some problems on Windows XP, but since these ETW functions only work +// on Vista+ that doesn't matter. +static __declspec( thread ) int s_nDepth; + +int64 ETWBegin( const char *pMessage ) +{ + // If we are running on Windows XP or if our providers have not been enabled + // (by xperf or other) then this will be false and we can early out. + // Be sure to check the appropriate context for the event. This is only + // worth checking if there is some cost beyond the EventWrite that we can + // avoid -- the redirectors in this file guarantee that EventWrite is always + // safe to call. + // In this case we also avoid the potentially unreliable TLS implementation + // (for dynamically loaded DLLs) on Windows XP. + if ( !VALVE_MAIN_Context.IsEnabled ) + { + return 0; + } + + int64 nTime = GetQPCTime(); + EventWriteStart( pMessage, s_nDepth++ ); + return nTime; +} + +int64 ETWEnd( const char *pMessage, int64 nStartTime ) +{ + // If we are running on Windows XP or if our providers have not been enabled + // (by xperf or other) then this will be false and we can early out. + // Be sure to check the appropriate context for the event. This is only + // worth checking if there is some cost beyond the EventWrite that we can + // avoid -- the redirectors in this file guarantee that EventWrite is always + // safe to call. + // In this case we also avoid the potentially unreliable TLS implementation + // (for dynamically loaded DLLs) on Windows XP. + if ( !VALVE_MAIN_Context.IsEnabled ) + { + return 0; + } + + int64 nTime = GetQPCTime(); + EventWriteStop( pMessage, --s_nDepth, QPCToMS( nTime - nStartTime ) ); + return nTime; +} + +// Record server and client frame counts separately, in case they are +// in the same process. +static int s_nRenderFrameCount[2]; + +int ETWGetRenderFrameNumber() +{ + return s_nRenderFrameCount[0]; +} + +// Insert a render frame marker using the Valve-FrameRate provider. Automatically +// count the frame number and frame time. +void ETWRenderFrameMark( bool bIsServerProcess ) +{ + Assert( bIsServerProcess == false || bIsServerProcess == true ); + // Record server and client frame counts separately, in case they are + // in the same process. + static int64 s_lastFrameTime[2]; + + int64 nCurrentFrameTime = GetQPCTime(); + float flElapsedFrameTime = 0.0f; + if ( s_nRenderFrameCount[bIsServerProcess] ) + { + flElapsedFrameTime = QPCToMS( nCurrentFrameTime - s_lastFrameTime[bIsServerProcess] ); + } + + if ( bIsServerProcess ) + { + EventWriteServerRenderFrameMark( s_nRenderFrameCount[bIsServerProcess], flElapsedFrameTime ); + } + else + { + EventWriteRenderFrameMark( s_nRenderFrameCount[bIsServerProcess], flElapsedFrameTime ); + } + + ++s_nRenderFrameCount[bIsServerProcess]; + s_lastFrameTime[bIsServerProcess] = nCurrentFrameTime; +} + +// Insert a simulation frame marker using the Valve-FrameRate provider. Automatically +// count the frame number and frame time. +void ETWSimFrameMark( bool bIsServerProcess ) +{ + Assert( bIsServerProcess == false || bIsServerProcess == true ); + // Record server and client frame counts separately, in case they are + // in the same process. + static int s_nFrameCount[2]; + static int64 s_lastFrameTime[2]; + + int64 nCurrentFrameTime = GetQPCTime(); + float flElapsedFrameTime = 0.0f; + if ( s_nFrameCount[bIsServerProcess] ) + { + flElapsedFrameTime = QPCToMS( nCurrentFrameTime - s_lastFrameTime[bIsServerProcess] ); + } + + if ( bIsServerProcess ) + { + EventWriteServerSimFrameMark( s_nFrameCount[bIsServerProcess], flElapsedFrameTime ); + } + else + { + EventWriteSimFrameMark( s_nFrameCount[bIsServerProcess], flElapsedFrameTime ); + } + + ++s_nFrameCount[bIsServerProcess]; + s_lastFrameTime[bIsServerProcess] = nCurrentFrameTime; +} + +void ETWMouseDown( int whichButton, int x, int y ) +{ + // Always have x/y first to make the summary tables easier to read. + EventWriteMouse_down( x, y, whichButton ); +} + +void ETWMouseUp( int whichButton, int x, int y ) +{ + // Always have x/y first to make the summary tables easier to read. + EventWriteMouse_up( x, y, whichButton ); +} + +void ETWMouseMove( int nX, int nY ) +{ + static int lastX, lastY; + + // Only emit mouse-move events if the mouse position has changed, since + // otherwise source2 emits a continous stream of events which makes it + // harder to find 'real' mouse-move events. + if ( lastX != nX || lastY != nY ) + { + lastX = nX; + lastY = nY; + // Always have x/y first to make the summary tables easier to read. + EventWriteMouse_Move( nX, nY ); + } +} + +void ETWMouseWheel( int nWheelDelta, int nX, int nY ) +{ + // Always have x/y first to make the summary tables easier to read. + EventWriteMouse_Wheel( nX, nY, nWheelDelta ); +} + +void ETWKeyDown( int nScanCode, int nVirtualCode, const char *pChar ) +{ + EventWriteKey_down( pChar, nScanCode, nVirtualCode ); +} + +void ETWSendPacket( const char *pTo, int nWireSize, int nOutSequenceNR, int nOutSequenceNrAck ) +{ + static int s_nCumulativeWireSize; + s_nCumulativeWireSize += nWireSize; + + EventWriteSendPacket( pTo, nWireSize, nOutSequenceNR, nOutSequenceNrAck, s_nCumulativeWireSize ); +} + +void ETWThrottled() +{ + EventWriteThrottled(); +} + +void ETWReadPacket( const char *pFrom, int nWireSize, int nInSequenceNR, int nOutSequenceNRAck ) +{ + static int s_nCumulativeWireSize; + s_nCumulativeWireSize += nWireSize; + + EventWriteReadPacket( pFrom, nWireSize, nInSequenceNR, nOutSequenceNRAck, s_nCumulativeWireSize ); +} + +#endif // ETW_MARKS_ENABLED diff --git a/tier0/extendedtrace.cpp b/tier0/extendedtrace.cpp new file mode 100644 index 0000000..1e09478 --- /dev/null +++ b/tier0/extendedtrace.cpp @@ -0,0 +1,407 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +////////////////////////////////////////////////////////////////////////////////////// +// +// Written by Zoltan Csizmadia, zoltan_csizmadia@yahoo.com +// For companies(Austin,TX): If you would like to get my resume, send an email. +// +// The source is free, but if you want to use it, mention my name and e-mail address +// +// History: +// 1.0 Initial version Zoltan Csizmadia +// +////////////////////////////////////////////////////////////////////////////////////// +// +// ExtendedTrace.cpp +// + +// Include StdAfx.h, if you're using precompiled +// header through StdAfx.h +//#include "stdafx.h" + +#if defined(_DEBUG) && defined(WIN32) + +#include "tier0/valve_off.h" +#include +#include +#include +#include +#include "tier0/valve_on.h" +#include "ExtendedTrace.h" + +#define BUFFERSIZE 0x200 + + +extern void OutputDebugStringFormat( const char *pMsg, ... ); + + +// Unicode safe char* -> TCHAR* conversion +void PCSTR2LPTSTR( PCSTR lpszIn, LPTSTR lpszOut ) +{ +#if defined(UNICODE)||defined(_UNICODE) + ULONG index = 0; + PCSTR lpAct = lpszIn; + + for( ; ; lpAct++ ) + { + lpszOut[index++] = (TCHAR)(*lpAct); + if ( *lpAct == 0 ) + break; + } +#else + // This is trivial :) + strcpy( lpszOut, lpszIn ); +#endif +} + +// Let's figure out the path for the symbol files +// Search path= ".;%_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%;%SYSTEMROOT%;%SYSTEMROOT%\System32;" + lpszIniPath +// Note: There is no size check for lpszSymbolPath! +void InitSymbolPath( PSTR lpszSymbolPath, PCSTR lpszIniPath ) +{ + CHAR lpszPath[BUFFERSIZE]; + + // Creating the default path + // ".;%_NT_SYMBOL_PATH%;%_NT_ALTERNATE_SYMBOL_PATH%;%SYSTEMROOT%;%SYSTEMROOT%\System32;" + strcpy( lpszSymbolPath, "." ); + + // environment variable _NT_SYMBOL_PATH + if ( GetEnvironmentVariableA( "_NT_SYMBOL_PATH", lpszPath, BUFFERSIZE ) ) + { + strcat( lpszSymbolPath, ";" ); + strcat( lpszSymbolPath, lpszPath ); + } + + // environment variable _NT_ALTERNATE_SYMBOL_PATH + if ( GetEnvironmentVariableA( "_NT_ALTERNATE_SYMBOL_PATH", lpszPath, BUFFERSIZE ) ) + { + strcat( lpszSymbolPath, ";" ); + strcat( lpszSymbolPath, lpszPath ); + } + + // environment variable SYSTEMROOT + if ( GetEnvironmentVariableA( "SYSTEMROOT", lpszPath, BUFFERSIZE ) ) + { + strcat( lpszSymbolPath, ";" ); + strcat( lpszSymbolPath, lpszPath ); + strcat( lpszSymbolPath, ";" ); + + // SYSTEMROOT\System32 + strcat( lpszSymbolPath, lpszPath ); + strcat( lpszSymbolPath, "\\System32" ); + } + + // Add user defined path + if ( lpszIniPath != NULL ) + if ( lpszIniPath[0] != '\0' ) + { + strcat( lpszSymbolPath, ";" ); + strcat( lpszSymbolPath, lpszIniPath ); + } +} + +// Uninitialize the loaded symbol files +BOOL UninitSymInfo() +{ + return SymCleanup( GetCurrentProcess() ); +} + +// Initializes the symbol files +BOOL InitSymInfo( PCSTR lpszInitialSymbolPath ) +{ + CHAR lpszSymbolPath[BUFFERSIZE]; + DWORD symOptions = SymGetOptions(); + + symOptions |= SYMOPT_LOAD_LINES; + symOptions &= ~SYMOPT_UNDNAME; + SymSetOptions( symOptions ); + + // Get the search path for the symbol files + InitSymbolPath( lpszSymbolPath, lpszInitialSymbolPath ); + + return SymInitialize( GetCurrentProcess(), lpszSymbolPath, TRUE); +} + +// Get the module name from a given address +BOOL GetModuleNameFromAddress( UINT address, LPTSTR lpszModule ) +{ + BOOL ret = FALSE; + IMAGEHLP_MODULE moduleInfo; + + ::ZeroMemory( &moduleInfo, sizeof(moduleInfo) ); + moduleInfo.SizeOfStruct = sizeof(moduleInfo); + + if ( SymGetModuleInfo( GetCurrentProcess(), (DWORD)address, &moduleInfo ) ) + { + // Got it! + PCSTR2LPTSTR( moduleInfo.ModuleName, lpszModule ); + ret = TRUE; + } + else + // Not found :( + _tcscpy( lpszModule, _T("?") ); + + return ret; +} + +// Get function prototype and parameter info from ip address and stack address +BOOL GetFunctionInfoFromAddresses( ULONG fnAddress, ULONG stackAddress, LPTSTR lpszSymbol ) +{ + BOOL ret = FALSE; + DWORD dwDisp = 0; + DWORD dwSymSize = 10000; + TCHAR lpszUnDSymbol[BUFFERSIZE]=_T("?"); + CHAR lpszNonUnicodeUnDSymbol[BUFFERSIZE]="?"; + LPTSTR lpszParamSep = NULL; + LPCTSTR lpszParsed = lpszUnDSymbol; + PIMAGEHLP_SYMBOL pSym = (PIMAGEHLP_SYMBOL)GlobalAlloc( GMEM_FIXED, dwSymSize ); + + ::ZeroMemory( pSym, dwSymSize ); + pSym->SizeOfStruct = dwSymSize; + pSym->MaxNameLength = dwSymSize - sizeof(IMAGEHLP_SYMBOL); + + // Set the default to unknown + _tcscpy( lpszSymbol, _T("?") ); + + // Get symbol info for IP + if ( SymGetSymFromAddr( GetCurrentProcess(), (ULONG)fnAddress, &dwDisp, pSym ) ) + { + // Make the symbol readable for humans + UnDecorateSymbolName( pSym->Name, lpszNonUnicodeUnDSymbol, BUFFERSIZE, + UNDNAME_COMPLETE | + UNDNAME_NO_THISTYPE | + UNDNAME_NO_SPECIAL_SYMS | + UNDNAME_NO_MEMBER_TYPE | + UNDNAME_NO_MS_KEYWORDS | + UNDNAME_NO_ACCESS_SPECIFIERS ); + + // Symbol information is ANSI string + PCSTR2LPTSTR( lpszNonUnicodeUnDSymbol, lpszUnDSymbol ); + + // I am just smarter than the symbol file :) + if ( _tcscmp(lpszUnDSymbol, _T("_WinMain@16")) == 0 ) + _tcscpy(lpszUnDSymbol, _T("WinMain(HINSTANCE,HINSTANCE,LPCTSTR,int)")); + else + if ( _tcscmp(lpszUnDSymbol, _T("_main")) == 0 ) + _tcscpy(lpszUnDSymbol, _T("main(int,TCHAR * *)")); + else + if ( _tcscmp(lpszUnDSymbol, _T("_mainCRTStartup")) == 0 ) + _tcscpy(lpszUnDSymbol, _T("mainCRTStartup()")); + else + if ( _tcscmp(lpszUnDSymbol, _T("_wmain")) == 0 ) + _tcscpy(lpszUnDSymbol, _T("wmain(int,TCHAR * *,TCHAR * *)")); + else + if ( _tcscmp(lpszUnDSymbol, _T("_wmainCRTStartup")) == 0 ) + _tcscpy(lpszUnDSymbol, _T("wmainCRTStartup()")); + + lpszSymbol[0] = _T('\0'); + + // Let's go through the stack, and modify the function prototype, and insert the actual + // parameter values from the stack + if ( _tcsstr( lpszUnDSymbol, _T("(void)") ) == NULL && _tcsstr( lpszUnDSymbol, _T("()") ) == NULL) + { + ULONG index = 0; + for( ; ; index++ ) + { + lpszParamSep = _tcschr( lpszParsed, _T(',') ); + if ( lpszParamSep == NULL ) + break; + + *lpszParamSep = _T('\0'); + + _tcscat( lpszSymbol, lpszParsed ); + _stprintf( lpszSymbol + _tcslen(lpszSymbol), _T("=0x%08X,"), *((ULONG*)(stackAddress) + 2 + index) ); + + lpszParsed = lpszParamSep + 1; + } + + lpszParamSep = _tcschr( lpszParsed, _T(')') ); + if ( lpszParamSep != NULL ) + { + *lpszParamSep = _T('\0'); + + _tcscat( lpszSymbol, lpszParsed ); + _stprintf( lpszSymbol + _tcslen(lpszSymbol), _T("=0x%08X)"), *((ULONG*)(stackAddress) + 2 + index) ); + + lpszParsed = lpszParamSep + 1; + } + } + + _tcscat( lpszSymbol, lpszParsed ); + + ret = TRUE; + } + + GlobalFree( pSym ); + + return ret; +} + +// Get source file name and line number from IP address +// The output format is: "sourcefile(linenumber)" or +// "modulename!address" or +// "address" +BOOL GetSourceInfoFromAddress( UINT address, LPTSTR lpszSourceInfo ) +{ + BOOL ret = FALSE; + IMAGEHLP_LINE lineInfo; + DWORD dwDisp; + TCHAR lpszFileName[BUFFERSIZE] = _T(""); + TCHAR lpModuleInfo[BUFFERSIZE] = _T(""); + + _tcscpy( lpszSourceInfo, _T("?(?)") ); + + ::ZeroMemory( &lineInfo, sizeof( lineInfo ) ); + lineInfo.SizeOfStruct = sizeof( lineInfo ); + + if ( SymGetLineFromAddr( GetCurrentProcess(), address, &dwDisp, &lineInfo ) ) + { + // Got it. Let's use "sourcefile(linenumber)" format + PCSTR2LPTSTR( lineInfo.FileName, lpszFileName ); + _stprintf( lpszSourceInfo, _T("%s(%d)"), lpszFileName, lineInfo.LineNumber ); + ret = TRUE; + } + else + { + // There is no source file information. :( + // Let's use the "modulename!address" format + GetModuleNameFromAddress( address, lpModuleInfo ); + + if ( lpModuleInfo[0] == _T('?') || lpModuleInfo[0] == _T('\0')) + // There is no modulename information. :(( + // Let's use the "address" format + _stprintf( lpszSourceInfo, _T("?") ); + else + _stprintf( lpszSourceInfo, _T("%s"), lpModuleInfo ); + + ret = FALSE; + } + + return ret; +} + +// TRACE message with source link. +// The format is: sourcefile(linenumber) : message +void SrcLinkTrace( LPCTSTR lpszMessage, LPCTSTR lpszFileName, ULONG nLineNumber ) +{ + OutputDebugStringFormat( + _T("%s(%d) : %s"), + lpszFileName, + nLineNumber, + lpszMessage ); +} + +void StackTrace( HANDLE hThread, LPCTSTR lpszMessage ) +{ + STACKFRAME callStack; + BOOL bResult; + CONTEXT context; + TCHAR symInfo[BUFFERSIZE] = _T("?"); + TCHAR srcInfo[BUFFERSIZE] = _T("?"); + HANDLE hProcess = GetCurrentProcess(); + + // If it's not this thread, let's suspend it, and resume it at the end + if ( hThread != GetCurrentThread() ) + if ( SuspendThread( hThread ) == -1 ) + { + // whaaat ?! + OutputDebugStringFormat( _T("Call stack info(thread=0x%X) failed.\n") ); + return; + } + + ::ZeroMemory( &context, sizeof(context) ); + context.ContextFlags = CONTEXT_FULL; + + if ( !GetThreadContext( hThread, &context ) ) + { + OutputDebugStringFormat( _T("Call stack info(thread=0x%X) failed.\n") ); + return; + } + + ::ZeroMemory( &callStack, sizeof(callStack) ); + callStack.AddrPC.Offset = context.Eip; + callStack.AddrStack.Offset = context.Esp; + callStack.AddrFrame.Offset = context.Ebp; + callStack.AddrPC.Mode = AddrModeFlat; + callStack.AddrStack.Mode = AddrModeFlat; + callStack.AddrFrame.Mode = AddrModeFlat; + + for( ULONG index = 0; ; index++ ) + { + bResult = StackWalk( + IMAGE_FILE_MACHINE_I386, + hProcess, + hThread, + &callStack, + NULL, + NULL, + SymFunctionTableAccess, + SymGetModuleBase, + NULL); + + if ( index == 0 ) + continue; + + if( !bResult || callStack.AddrFrame.Offset == 0 ) + break; + + GetFunctionInfoFromAddresses( callStack.AddrPC.Offset, callStack.AddrFrame.Offset, symInfo ); + GetSourceInfoFromAddress( callStack.AddrPC.Offset, srcInfo ); + + OutputDebugStringFormat( _T(" %s : %s\n"), srcInfo, symInfo ); + } + + if ( hThread != GetCurrentThread() ) + ResumeThread( hThread ); +} + +void FunctionParameterInfo() +{ + STACKFRAME callStack; + BOOL bResult = FALSE; + CONTEXT context; + TCHAR lpszFnInfo[BUFFERSIZE]; + HANDLE hProcess = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + + ::ZeroMemory( &context, sizeof(context) ); + context.ContextFlags = CONTEXT_FULL; + + if ( !GetThreadContext( hThread, &context ) ) + { + OutputDebugStringFormat( _T("Function info(thread=0x%X) failed.\n") ); + return; + } + + ::ZeroMemory( &callStack, sizeof(callStack) ); + callStack.AddrPC.Offset = context.Eip; + callStack.AddrStack.Offset = context.Esp; + callStack.AddrFrame.Offset = context.Ebp; + callStack.AddrPC.Mode = AddrModeFlat; + callStack.AddrStack.Mode = AddrModeFlat; + callStack.AddrFrame.Mode = AddrModeFlat; + + for( ULONG index = 0; index < 2; index++ ) + { + bResult = StackWalk( + IMAGE_FILE_MACHINE_I386, + hProcess, + hThread, + &callStack, + NULL, + NULL, + SymFunctionTableAccess, + SymGetModuleBase, + NULL); + } + + if ( bResult && callStack.AddrFrame.Offset != 0) + { + GetFunctionInfoFromAddresses( callStack.AddrPC.Offset, callStack.AddrFrame.Offset, lpszFnInfo ); + OutputDebugStringFormat( _T("Function info(thread=0x%X) : %s\n"), GetCurrentThreadId(), lpszFnInfo ); + } + else + OutputDebugStringFormat( _T("Function info(thread=0x%X) failed.\n") ); +} + +#endif //_DEBUG && WIN32 + diff --git a/tier0/extendedtrace.h b/tier0/extendedtrace.h new file mode 100644 index 0000000..6a410b1 --- /dev/null +++ b/tier0/extendedtrace.h @@ -0,0 +1,63 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +////////////////////////////////////////////////////////////////////////////////////// +// +// Written by Zoltan Csizmadia, zoltan_csizmadia@yahoo.com +// For companies(Austin,TX): If you would like to get my resume, send an email. +// +// The source is free, but if you want to use it, mention my name and e-mail address +// +// History: +// 1.0 Initial version Zoltan Csizmadia +// +////////////////////////////////////////////////////////////////////////////////////// +// +// ExtendedTrace.h +// + +#ifndef EXTENDEDTRACE_H_INCLUDED +#define EXTENDEDTRACE_H_INCLUDED + +#if defined(_DEBUG) && defined(WIN32) + + +#pragma comment( lib, "imagehlp.lib" ) + +#if defined(_AFX) || defined(_AFXDLL) +#define TRACEF TRACE +#else +#define TRACEF OutputDebugStringFormat +void OutputDebugStringFormat( PRINTF_FORMAT_STRING LPCTSTR, ... ); +#endif + +#define EXTENDEDTRACEINITIALIZE( IniSymbolPath ) InitSymInfo( IniSymbolPath ) +#define EXTENDEDTRACEUNINITIALIZE() UninitSymInfo() +#define SRCLINKTRACECUSTOM( Msg, File, Line) SrcLinkTrace( Msg, File, Line ) +#define SRCLINKTRACE( Msg ) SrcLinkTrace( Msg, __FILE__, __LINE__ ) +#define FNPARAMTRACE() FunctionParameterInfo() +#define STACKTRACEMSG( Msg ) StackTrace( Msg ) +#define STACKTRACE() StackTrace( GetCurrentThread(), _T("") ) +#define THREADSTACKTRACEMSG( hThread, Msg ) StackTrace( hThread, Msg ) +#define THREADSTACKTRACE( hThread ) StackTrace( hThread, _T("") ) + +BOOL InitSymInfo( PCSTR ); +BOOL UninitSymInfo(); +void SrcLinkTrace( LPCTSTR, LPCTSTR, ULONG ); +void StackTrace( HANDLE, LPCTSTR ); +void FunctionParameterInfo(); + +#else + +#define EXTENDEDTRACEINITIALIZE( IniSymbolPath ) ((void)0) +#define EXTENDEDTRACEUNINITIALIZE() ((void)0) +#define TRACEF ((void)0) +#define SRCLINKTRACECUSTOM( Msg, File, Line) ((void)0) +#define SRCLINKTRACE( Msg ) ((void)0) +#define FNPARAMTRACE() ((void)0) +#define STACKTRACEMSG( Msg ) ((void)0) +#define STACKTRACE() ((void)0) +#define THREADSTACKTRACEMSG( hThread, Msg ) ((void)0) +#define THREADSTACKTRACE( hThread ) ((void)0) + +#endif + +#endif \ No newline at end of file diff --git a/tier0/fasttimer.cpp b/tier0/fasttimer.cpp new file mode 100644 index 0000000..a4a7696 --- /dev/null +++ b/tier0/fasttimer.cpp @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" + +#include +#include "tier0/fasttimer.h" + +// g_dwClockSpeed is only exported for backwards compatibility. +PLATFORM_INTERFACE unsigned long g_dwClockSpeed; + +uint64 g_ClockSpeed; // Clocks/sec +// Storing CPU clock speed in a 32-bit variable is dangerous and can already overflow +// on some CPUs. This variable is deprecated. +unsigned long g_dwClockSpeed; +#if defined( _X360 ) && defined( _CERT ) +unsigned long g_dwFakeFastCounter; +#endif +double g_ClockSpeedMicrosecondsMultiplier; +double g_ClockSpeedMillisecondsMultiplier; +double g_ClockSpeedSecondsMultiplier; + +// Constructor init the clock speed. +CClockSpeedInit g_ClockSpeedInit CONSTRUCT_EARLY; + +void CClockSpeedInit::Init() +{ + const CPUInformation& cpuinfo = *GetCPUInformation(); + + g_ClockSpeed = cpuinfo.m_Speed; + + // cycle counter runs as doc'd at 1/64 Xbox 3.2GHz clock speed, thus 50 Mhz + if ( IsX360() ) + { + g_ClockSpeed /= 64L; + } + + // Avoid integer overflow when writing to g_dwClockSpeed + if ( g_ClockSpeed <= ULONG_MAX ) + g_dwClockSpeed = (unsigned long)g_ClockSpeed; + else + g_dwClockSpeed = ULONG_MAX; + + g_ClockSpeedMicrosecondsMultiplier = 1000000.0 / (double)g_ClockSpeed; + g_ClockSpeedMillisecondsMultiplier = 1000.0 / (double)g_ClockSpeed; + g_ClockSpeedSecondsMultiplier = 1.0 / (double)g_ClockSpeed; +} diff --git a/tier0/mem.cpp b/tier0/mem.cpp new file mode 100644 index 0000000..3e716d5 --- /dev/null +++ b/tier0/mem.cpp @@ -0,0 +1,96 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" +#include "tier0/mem.h" +#include +#include "tier0/dbg.h" +#include "tier0/minidump.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#ifndef STEAM +#define PvRealloc realloc +#define PvAlloc malloc +#define PvExpand _expand +#endif + +enum +{ + MAX_STACK_DEPTH = 32 +}; + +static uint8 *s_pBuf = NULL; +static int s_pBufStackDepth[MAX_STACK_DEPTH]; +static int s_nBufDepth = -1; +static int s_nBufCurSize = 0; +static int s_nBufAllocSize = 0; +static bool s_oomerror_called = false; + +void MemAllocOOMError( size_t nSize ) +{ + if ( !s_oomerror_called ) + { + s_oomerror_called = true; + + MinidumpUserStreamInfoAppend( "MemAllocOOMError: %u bytes\n", (uint)nSize ); + + //$ TODO: Need a good error message here. + // A basic advice to try lowering texture settings is just most-likely to help users who are exhausting address + // space, but not necessarily the cause. Ideally the engine wouldn't let you get here because of too-high settings. + Error( "Out of memory or address space. Texture quality setting may be too high.\n" ); + } +} + +//----------------------------------------------------------------------------- +// Other DLL-exported methods for particular kinds of memory +//----------------------------------------------------------------------------- +void *MemAllocScratch( int nMemSize ) +{ + // Minimally allocate 1M scratch + if (s_nBufAllocSize < s_nBufCurSize + nMemSize) + { + s_nBufAllocSize = s_nBufCurSize + nMemSize; + if (s_nBufAllocSize < 1024 * 1024) + { + s_nBufAllocSize = 1024 * 1024; + } + + if (s_pBuf) + { + s_pBuf = (uint8*)PvRealloc( s_pBuf, s_nBufAllocSize ); + Assert( s_pBuf ); + } + else + { + s_pBuf = (uint8*)PvAlloc( s_nBufAllocSize ); + } + } + + int nBase = s_nBufCurSize; + s_nBufCurSize += nMemSize; + ++s_nBufDepth; + Assert( s_nBufDepth < MAX_STACK_DEPTH ); + s_pBufStackDepth[s_nBufDepth] = nMemSize; + + return &s_pBuf[nBase]; +} + +void MemFreeScratch() +{ + Assert( s_nBufDepth >= 0 ); + s_nBufCurSize -= s_pBufStackDepth[s_nBufDepth]; + --s_nBufDepth; +} + +#ifdef POSIX +void ZeroMemory( void *mem, size_t length ) +{ + memset( mem, 0x0, length ); +} +#endif diff --git a/tier0/mem_helpers.cpp b/tier0/mem_helpers.cpp new file mode 100644 index 0000000..b55c4e4 --- /dev/null +++ b/tier0/mem_helpers.cpp @@ -0,0 +1,172 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "pch_tier0.h" +#include "mem_helpers.h" +#include +#include + +bool g_bInitMemory = true; + +#ifdef POSIX +void DoApplyMemoryInitializations( void *pMem, int nSize ) +{ +} + +size_t CalcHeapUsed() +{ + return 0; +} + +#else + +unsigned long g_dwFeeFee = 0xffeeffee; + +// Generated by Mathematica. +unsigned char g_RandomValues[256] = { + 95, 126, 220, 71, 92, 179, 95, 219, 111, 150, 38, 155, 181, 62, 40, 231, 238, + 54, 47, 55, 186, 204, 64, 70, 118, 94, 107, 251, 199, 140, 67, 87, 86, 127, + 210, 41, 21, 90, 208, 24, 167, 204, 32, 254, 38, 51, 9, 11, 38, 33, 188, 104, + 0, 75, 119, 24, 122, 203, 24, 164, 250, 224, 241, 182, 213, 201, 173, 67, + 200, 255, 244, 227, 46, 219, 26, 149, 218, 132, 120, 154, 227, 244, 106, 198, + 109, 87, 150, 40, 16, 99, 169, 193, 100, 156, 78, 171, 246, 47, 84, 119, 10, + 52, 207, 171, 230, 90, 90, 127, 180, 153, 68, 140, 62, 14, 87, 57, 208, 154, + 116, 29, 131, 177, 224, 187, 51, 148, 142, 245, 152, 230, 184, 117, 91, 146, + 235, 153, 35, 104, 187, 177, 215, 131, 17, 49, 211, 244, 60, 152, 103, 248, + 51, 224, 237, 240, 51, 30, 10, 233, 253, 106, 252, 73, 134, 136, 178, 86, + 228, 107, 77, 255, 85, 242, 204, 119, 102, 53, 209, 35, 123, 32, 252, 210, + 43, 12, 136, 167, 155, 210, 71, 254, 178, 172, 3, 230, 93, 208, 196, 68, 235, + 16, 106, 189, 201, 177, 85, 78, 206, 187, 48, 68, 64, 190, 117, 236, 49, 174, + 105, 63, 207, 70, 170, 93, 6, 110, 52, 111, 169, 92, 247, 86, 10, 174, 207, + 240, 104, 209, 81, 177, 123, 189, 175, 212, 101, 219, 114, 243, 44, 91, 51, + 139, 91, 57, 120, 41, 98, 119 }; + +unsigned long g_iCurRandomValueOffset = 0; + + +void InitializeToFeeFee( void *pMem, int nSize ) +{ + unsigned long *pCurDWord = (unsigned long*)pMem; + int nDWords = nSize >> 2; + while ( nDWords ) + { + *pCurDWord = 0xffeeffee; + ++pCurDWord; + --nDWords; + } + + unsigned char *pCurChar = (unsigned char*)pCurDWord; + int nBytes = nSize & 3; + int iOffset = 0; + while ( nBytes ) + { + *pCurChar = ((unsigned char*)&g_dwFeeFee)[iOffset]; + ++iOffset; + --nBytes; + ++pCurChar; + } +} + + +void InitializeToRandom( void *pMem, int nSize ) +{ + unsigned char *pOut = (unsigned char *)pMem; + for ( int i=0; i < nSize; i++ ) + { + pOut[i] = g_RandomValues[(g_iCurRandomValueOffset & 255)]; + ++g_iCurRandomValueOffset; + } +} + + +void DoApplyMemoryInitializations( void *pMem, int nSize ) +{ + if ( !pMem ) + return; + + // If they passed -noinitmemory on the command line, don't do anything here. + Assert( g_bInitMemory ); + + // First time we get in here, remember all the settings. + static bool bDebuggerPresent = Plat_IsInDebugSession(); + static bool bCheckedCommandLine = false; + static bool bRandomizeMemory = false; + if ( !bCheckedCommandLine ) + { + bCheckedCommandLine = true; + + //APS + char *pStr = (char*)Plat_GetCommandLineA(); + if ( pStr ) + { + char tempStr[512]; + strncpy( tempStr, pStr, sizeof( tempStr ) - 1 ); + tempStr[ sizeof( tempStr ) - 1 ] = 0; + _strupr( tempStr ); + + if ( strstr( tempStr, "-RANDOMIZEMEMORY" ) ) + bRandomizeMemory = true; + + if ( strstr( tempStr, "-NOINITMEMORY" ) ) + g_bInitMemory = false; + } + } + + if ( bRandomizeMemory ) + { + // They asked for it.. randomize all the memory. + InitializeToRandom( pMem, nSize ); + } + else + { + if ( bDebuggerPresent ) + { + // Ok, it's already set to 0xbaadf00d, but we want something that will make floating-point #'s NANs. + InitializeToFeeFee( pMem, nSize ); + } + else + { +#ifdef _DEBUG + // Ok, it's already set to 0xcdcdcdcd, but we want something that will make floating-point #'s NANs. + InitializeToFeeFee( pMem, nSize ); +#endif + } + } +} + +size_t CalcHeapUsed() +{ +#if defined( _X360 ) + return 0; +#else + _HEAPINFO hinfo; + int heapstatus; + intp nTotal; + + nTotal = 0; + hinfo._pentry = NULL; + while( ( heapstatus = _heapwalk( &hinfo ) ) == _HEAPOK ) + { + nTotal += (hinfo._useflag == _USEDENTRY) ? hinfo._size : 0; + } + + switch (heapstatus) + { + case _HEAPEMPTY: + case _HEAPEND: + // success + break; + + default: + // heap corrupted + nTotal = -1; + } + + return nTotal; +#endif +} +#endif + diff --git a/tier0/mem_helpers.h b/tier0/mem_helpers.h new file mode 100644 index 0000000..a7656d7 --- /dev/null +++ b/tier0/mem_helpers.h @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MEM_HELPERS_H +#define MEM_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +// Normally, the runtime libraries like to mess with the memory returned by malloc(), +// which can create problems trying to repro bugs in debug builds or in the debugger. +// +// If the debugger is present, it initializes data to 0xbaadf00d, which makes floating +// point numbers come out to about 0.1. +// +// If the debugger is not present, and it's a debug build, then you get 0xcdcdcdcd, +// which is about 25 million. +// +// Otherwise, you get uninitialized memory. +// +// In here, we make sure the memory is either random garbage, or it's set to +// 0xffeeffee, which casts to a NAN. +extern bool g_bInitMemory; +#define ApplyMemoryInitializations( pMem, nSize ) if ( !g_bInitMemory ) ; else { DoApplyMemoryInitializations( pMem, nSize ); } +void DoApplyMemoryInitializations( void *pMem, int nSize ); + +size_t CalcHeapUsed(); + +// Call this to reserve the bottom 4 GB of memory in order to ensure that we will +// get crashes if we put pointers in DWORDs or ints. This will be a NOP on some +// platforms (Xbox 360, PS3, 32-bit Windows, etc.) +void ReserveBottomMemory(); + +#endif // MEM_HELPERS_H diff --git a/tier0/memdbg.cpp b/tier0/memdbg.cpp new file mode 100644 index 0000000..ef23e9a --- /dev/null +++ b/tier0/memdbg.cpp @@ -0,0 +1,1955 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//=============================================================================// + + +#include "pch_tier0.h" + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +#include +#include +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "mem_helpers.h" +#ifdef _WIN32 +#include +#endif +#ifdef OSX +#include +#include +#include +#endif + +#include +#include +#include +#include "tier0/threadtools.h" +#ifdef _X360 +#include "xbox/xbox_console.h" +#endif +#if ( !defined(_DEBUG) && defined(USE_MEM_DEBUG) ) +#pragma message ("USE_MEM_DEBUG is enabled in a release build. Don't check this in!") +#endif +#if (defined(_DEBUG) || defined(USE_MEM_DEBUG)) + +#if defined(_WIN32) && ( !defined(_X360) && !defined(_WIN64) ) +// #define USE_STACK_WALK +// or: +// #define USE_STACK_WALK_DETAILED +#endif + +//----------------------------------------------------------------------------- + +#ifndef _X360 +#define DebugAlloc malloc +#define DebugFree free +#else +#define DebugAlloc DmAllocatePool +#define DebugFree DmFreePool +#endif + +#ifdef WIN32 +int g_DefaultHeapFlags = _CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | _CRTDBG_ALLOC_MEM_DF ); +#endif + +#if defined( _MEMTEST ) +static char s_szStatsMapName[32]; +static char s_szStatsComment[256]; +#endif + +//----------------------------------------------------------------------------- + +#if defined( USE_STACK_WALK ) || defined( USE_STACK_WALK_DETAILED ) +#include + +#pragma comment(lib, "Dbghelp.lib" ) + +#pragma auto_inline(off) +__declspec(naked) DWORD GetEIP() +{ + __asm + { + mov eax, [ebp + 4] + ret + } +} + +int WalkStack( void **ppAddresses, int nMaxAddresses, int nSkip = 0 ) +{ + HANDLE hProcess = GetCurrentProcess(); + HANDLE hThread = GetCurrentThread(); + + STACKFRAME64 frame; + + memset(&frame, 0, sizeof(frame)); + DWORD valEsp, valEbp; + __asm + { + mov [valEsp], esp; + mov [valEbp], ebp + } + frame.AddrPC.Offset = GetEIP(); + frame.AddrStack.Offset = valEsp; + frame.AddrFrame.Offset = valEbp; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrStack.Mode = AddrModeFlat; + frame.AddrFrame.Mode = AddrModeFlat; + + // Walk the stack. + int nWalked = 0; + nSkip++; + while ( nMaxAddresses - nWalked > 0 ) + { + if ( !StackWalk64(IMAGE_FILE_MACHINE_I386, hProcess, hThread, &frame, NULL, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL ) ) + { + break; + } + + if ( nSkip == 0 ) + { + if (frame.AddrFrame.Offset == 0) + { + // End of stack. + break; + } + + *ppAddresses++ = (void *)frame.AddrPC.Offset; + nWalked++; + + if (frame.AddrPC.Offset == frame.AddrReturn.Offset) + { + // Catching a stack loop + break; + } + } + else + { + nSkip--; + } + } + + if ( nMaxAddresses ) + { + memset( ppAddresses, 0, ( nMaxAddresses - nWalked ) * sizeof(*ppAddresses) ); + } + + return nWalked; +} + +bool GetModuleFromAddress( void *address, char *pResult ) +{ + IMAGEHLP_MODULE moduleInfo; + + moduleInfo.SizeOfStruct = sizeof(moduleInfo); + + if ( SymGetModuleInfo( GetCurrentProcess(), (DWORD)address, &moduleInfo ) ) + { + strcpy( pResult, moduleInfo.ModuleName ); + return true; + } + + return false; +} + +bool GetCallerModule( char *pDest ) +{ + static bool bInit; + if ( !bInit ) + { + PSTR psUserSearchPath = NULL; + psUserSearchPath = "u:\\data\\game\\bin\\;u:\\data\\game\\episodic\\bin\\;u:\\data\\game\\hl2\\bin\\;\\\\perforce\\symbols"; + SymInitialize( GetCurrentProcess(), psUserSearchPath, true ); + bInit = true; + } + void *pCaller; + WalkStack( &pCaller, 1, 2 ); + + return ( pCaller != 0 && GetModuleFromAddress( pCaller, pDest ) ); +} + + +#if defined( USE_STACK_WALK_DETAILED ) + +// +// Note: StackDescribe function is non-reentrant: +// Reason: Stack description is stored in a static buffer. +// Solution: Passing caller-allocated buffers would allow the +// function to become reentrant, however the current only client (FindOrCreateFilename) +// is synchronized with a heap mutex, after retrieving stack description the +// heap memory will be allocated to copy the text. +// + +char * StackDescribe( void **ppAddresses, int nMaxAddresses ) +{ + static char s_chStackDescription[ 32 * 1024 ]; + static char s_chSymbolBuffer[ sizeof( IMAGEHLP_SYMBOL64 ) + 1024 ]; + + IMAGEHLP_SYMBOL64 &hlpSymbol = * ( IMAGEHLP_SYMBOL64 * ) s_chSymbolBuffer; + hlpSymbol.SizeOfStruct = sizeof( IMAGEHLP_SYMBOL64 ); + hlpSymbol.MaxNameLength = 1024; + DWORD64 hlpSymbolOffset = 0; + + IMAGEHLP_LINE64 hlpLine; + hlpLine.SizeOfStruct = sizeof( IMAGEHLP_LINE64 ); + DWORD hlpLineOffset = 0; + + s_chStackDescription[ 0 ] = 0; + char *pchBuffer = s_chStackDescription; + + for ( int k = 0; k < nMaxAddresses; ++ k ) + { + if ( !ppAddresses[k] ) + break; + + pchBuffer += strlen( pchBuffer ); + if ( SymGetLineFromAddr64( GetCurrentProcess(), ( DWORD64 ) ppAddresses[k], &hlpLineOffset, &hlpLine ) ) + { + char const *pchFileName = hlpLine.FileName ? hlpLine.FileName + strlen( hlpLine.FileName ) : NULL; + for ( size_t numSlashesAllowed = 2; pchFileName > hlpLine.FileName; -- pchFileName ) + { + if ( *pchFileName == '\\' ) + { + if ( numSlashesAllowed -- ) + continue; + else + break; + } + } + sprintf( pchBuffer, hlpLineOffset ? "%s:%d+0x%I32X" : "%s:%d", pchFileName, hlpLine.LineNumber, hlpLineOffset ); + } + else if ( SymGetSymFromAddr64( GetCurrentProcess(), ( DWORD64 ) ppAddresses[k], &hlpSymbolOffset, &hlpSymbol ) ) + { + sprintf( pchBuffer, ( hlpSymbolOffset > 0 && !( hlpSymbolOffset >> 63 ) ) ? "%s+0x%I64X" : "%s", hlpSymbol.Name, hlpSymbolOffset ); + } + else + { + sprintf( pchBuffer, "#0x%08p", ppAddresses[k] ); + } + + pchBuffer += strlen( pchBuffer ); + sprintf( pchBuffer, "<--" ); + } + *pchBuffer = 0; + + return s_chStackDescription; +} + +#endif // #if defined( USE_STACK_WALK_DETAILED ) + +#else + +inline int WalkStack( void **ppAddresses, int nMaxAddresses, int nSkip = 0 ) +{ + memset( ppAddresses, 0, nMaxAddresses * sizeof(*ppAddresses) ); + return 0; +} +#define GetModuleFromAddress( address, pResult ) ( ( *pResult = 0 ), 0) +#define GetCallerModule( pDest ) false +#endif + + +//----------------------------------------------------------------------------- + +// NOTE: This exactly mirrors the dbg header in the MSDEV crt +// eventually when we write our own allocator, we can kill this +struct CrtDbgMemHeader_t +{ + unsigned char m_Reserved[8]; + const char *m_pFileName; + int m_nLineNumber; + unsigned char m_Reserved2[16]; +}; + +struct DbgMemHeader_t +#if !defined( _DEBUG ) || defined( POSIX ) + : CrtDbgMemHeader_t +#endif +{ + unsigned nLogicalSize; + byte reserved[12]; // MS allocator always returns mem aligned on 16 bytes, which some of our code depends on +}; + +//----------------------------------------------------------------------------- + +#if defined( _DEBUG ) && !defined( POSIX ) +#define GetCrtDbgMemHeader( pMem ) ((CrtDbgMemHeader_t*)((DbgMemHeader_t*)pMem - 1) - 1) +#elif defined( OSX ) +DbgMemHeader_t *GetCrtDbgMemHeader( void *pMem ); +#else +#define GetCrtDbgMemHeader( pMem ) ((DbgMemHeader_t*)pMem - 1) +#endif + +#ifdef OSX +DbgMemHeader_t *GetCrtDbgMemHeader( void *pMem ) +{ + size_t msize = malloc_size( pMem ); + return (DbgMemHeader_t *)( (char *)pMem + msize - sizeof(DbgMemHeader_t) ); +} +#endif + +inline void *InternalMalloc( size_t nSize, const char *pFileName, int nLine ) +{ +#ifdef OSX + void *pAllocedMem = malloc_zone_malloc( malloc_default_zone(), nSize + sizeof(DbgMemHeader_t) ); + if (!pAllocedMem) + { + return NULL; + } + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pAllocedMem ); + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = nSize; + *((int*)pInternalMem->m_Reserved) = 0xf00df00d; + + return pAllocedMem; +#else // LINUX || WIN32 + DbgMemHeader_t *pInternalMem; +#if defined( POSIX ) || !defined( _DEBUG ) + pInternalMem = (DbgMemHeader_t *)malloc( nSize + sizeof(DbgMemHeader_t) ); + if (!pInternalMem) + { + return NULL; + } + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + *((int*)pInternalMem->m_Reserved) = 0xf00df00d; +#else + pInternalMem = (DbgMemHeader_t *)_malloc_dbg( nSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); +#endif // defined( POSIX ) || !defined( _DEBUG ) + + pInternalMem->nLogicalSize = nSize; + return pInternalMem + 1; +#endif // LINUX || WIN32 +} + +inline void *InternalRealloc( void *pMem, size_t nNewSize, const char *pFileName, int nLine ) +{ + if ( !pMem ) + return InternalMalloc( nNewSize, pFileName, nLine ); + +#ifdef OSX + void *pNewAllocedMem = NULL; + + pNewAllocedMem = (void *)malloc_zone_realloc( malloc_default_zone(), pMem, nNewSize + sizeof(DbgMemHeader_t) ); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pNewAllocedMem ); + + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; + pInternalMem->nLogicalSize = static_cast( nNewSize ); + *((int*)pInternalMem->m_Reserved) = 0xf00df00d; + + return pNewAllocedMem; +#else // LINUX || WIN32 + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#if defined( POSIX ) || !defined( _DEBUG ) + pInternalMem = (DbgMemHeader_t *)realloc( pInternalMem, nNewSize + sizeof(DbgMemHeader_t) ); + pInternalMem->m_pFileName = pFileName; + pInternalMem->m_nLineNumber = nLine; +#else + pInternalMem = (DbgMemHeader_t *)_realloc_dbg( pInternalMem, nNewSize + sizeof(DbgMemHeader_t), _NORMAL_BLOCK, pFileName, nLine ); +#endif + + pInternalMem->nLogicalSize = nNewSize; + return pInternalMem + 1; +#endif // LINUX || WIN32 +} + +inline void InternalFree( void *pMem ) +{ + if ( !pMem ) + return; + + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#if !defined( _DEBUG ) || defined( POSIX ) +#ifdef OSX + malloc_zone_free( malloc_default_zone(), pMem ); +#elif LINUX + free( pInternalMem ); +#else + free( pInternalMem ); +#endif +#else + _free_dbg( pInternalMem, _NORMAL_BLOCK ); +#endif +} + +inline size_t InternalMSize( void *pMem ) +{ + //$ TODO. For Linux, we could use 'int size = malloc_usable_size( pMem )'... +#if defined(POSIX) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); + return pInternalMem->nLogicalSize; +#elif !defined(_DEBUG) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); + return _msize( pInternalMem ) - sizeof(DbgMemHeader_t); +#else + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; + return _msize_dbg( pInternalMem, _NORMAL_BLOCK ) - sizeof(DbgMemHeader_t); +#endif +} + +inline size_t InternalLogicalSize( void *pMem ) +{ +#if defined(POSIX) + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( pMem ); +#elif !defined(_DEBUG) + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#else + DbgMemHeader_t *pInternalMem = (DbgMemHeader_t *)pMem - 1; +#endif + return pInternalMem->nLogicalSize; +} + +#ifndef _DEBUG +#define _CrtDbgReport( nRptType, szFile, nLine, szModule, pMsg ) 0 +#endif + + +//----------------------------------------------------------------------------- + + +// Custom allocator protects this module from recursing on operator new +template +class CNoRecurseAllocator +{ +public: + // type definitions + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + CNoRecurseAllocator() {} + CNoRecurseAllocator(const CNoRecurseAllocator&) {} + template CNoRecurseAllocator(const CNoRecurseAllocator&) {} + ~CNoRecurseAllocator(){} + + // rebind allocator to type U + template struct rebind { typedef CNoRecurseAllocator other; }; + + // return address of values + pointer address (reference value) const { return &value; } + + const_pointer address (const_reference value) const { return &value;} + size_type max_size() const { return INT_MAX; } + + pointer allocate(size_type num, const void* = 0) { return (pointer)DebugAlloc(num * sizeof(T)); } + void deallocate (pointer p, size_type num) { DebugFree(p); } + void construct(pointer p, const T& value) { new((void*)p)T(value); } + void destroy (pointer p) { p->~T(); } +}; + +template +bool operator==(const CNoRecurseAllocator&, const CNoRecurseAllocator&) +{ + return true; +} + +template +bool operator!=(const CNoRecurseAllocator&, const CNoRecurseAllocator&) +{ + return false; +} + +class CStringLess +{ +public: + bool operator()(const char *pszLeft, const char *pszRight ) const + { + return ( stricmp( pszLeft, pszRight ) < 0 ); + } +}; + +//----------------------------------------------------------------------------- + +#pragma warning( disable:4074 ) // warning C4074: initializers put in compiler reserved initialization area +#pragma init_seg( compiler ) + +//----------------------------------------------------------------------------- +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually +//----------------------------------------------------------------------------- +class CDbgMemAlloc : public IMemAlloc +{ +public: + CDbgMemAlloc(); + virtual ~CDbgMemAlloc(); + + // Release versions + virtual void *Alloc( size_t nSize ); + virtual void *Realloc( void *pMem, size_t nSize ); + virtual void Free( void *pMem ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ); + + // Debug versions + virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ); + virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ); + virtual void Free( void *pMem, const char *pFileName, int nLine ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ); + + // Returns size of a particular allocation + virtual size_t GetSize( void *pMem ); + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo( const char *pFileName, int nLine ); + virtual void PopAllocDbgInfo(); + + virtual long CrtSetBreakAlloc( long lNewBreakAlloc ); + virtual int CrtSetReportMode( int nReportType, int nReportMode ); + virtual int CrtIsValidHeapPointer( const void *pMem ); + virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ); + virtual int CrtCheckMemory( void ); + virtual int CrtSetDbgFlag( int nNewFlag ); + virtual void CrtMemCheckpoint( _CrtMemState *pState ); + + // handles storing allocation info for coroutines + virtual uint32 GetDebugInfoSize(); + virtual void SaveDebugInfo( void *pvDebugInfo ); + virtual void RestoreDebugInfo( const void *pvDebugInfo ); + virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ); + + // FIXME: Remove when we have our own allocator + virtual void* CrtSetReportFile( int nRptType, void* hFile ); + virtual void* CrtSetReportHook( void* pfnNewHook ); + virtual int CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * szFormat ); + + virtual int heapchk(); + + virtual bool IsDebugHeap() { return true; } + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void CompactHeap() + { +#if defined( _X360 ) && defined( _DEBUG ) + HeapCompact( GetProcessHeap(), 0 ); +#endif + } + + virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) { return NULL; } // debug heap doesn't attempt retries + +#if defined( _MEMTEST ) + void SetStatsExtraInfo( const char *pMapName, const char *pComment ) + { + strncpy( s_szStatsMapName, pMapName, sizeof( s_szStatsMapName ) ); + s_szStatsMapName[sizeof( s_szStatsMapName ) - 1] = '\0'; + + strncpy( s_szStatsComment, pComment, sizeof( s_szStatsComment ) ); + s_szStatsComment[sizeof( s_szStatsComment ) - 1] = '\0'; + } +#endif + + virtual size_t MemoryAllocFailed(); + void SetCRTAllocFailed( size_t nMemSize ); + + enum + { + BYTE_COUNT_16 = 0, + BYTE_COUNT_32, + BYTE_COUNT_128, + BYTE_COUNT_1024, + BYTE_COUNT_GREATER, + + NUM_BYTE_COUNT_BUCKETS + }; + + void Shutdown(); + +private: + struct MemInfo_t + { + MemInfo_t() + { + memset( this, 0, sizeof(*this) ); + } + + // Size in bytes + size_t m_nCurrentSize; + size_t m_nPeakSize; + size_t m_nTotalSize; + size_t m_nOverheadSize; + size_t m_nPeakOverheadSize; + + // Count in terms of # of allocations + size_t m_nCurrentCount; + size_t m_nPeakCount; + size_t m_nTotalCount; + + // Count in terms of # of allocations of a particular size + size_t m_pCount[NUM_BYTE_COUNT_BUCKETS]; + + // Time spent allocating + deallocating (microseconds) + int64 m_nTime; + }; + + struct MemInfoKey_t + { + MemInfoKey_t( const char *pFileName, int line ) : m_pFileName(pFileName), m_nLine(line) {} + bool operator<( const MemInfoKey_t &key ) const + { + int iret = stricmp( m_pFileName, key.m_pFileName ); + if ( iret < 0 ) + return true; + + if ( iret > 0 ) + return false; + + return m_nLine < key.m_nLine; + } + + const char *m_pFileName; + int m_nLine; + }; + + // NOTE: Deliberately using STL here because the UTL stuff + // is a client of this library; want to avoid circular dependency + + // Maps file name to info + typedef std::map< MemInfoKey_t, MemInfo_t, std::less, CNoRecurseAllocator > > StatMap_t; + typedef StatMap_t::iterator StatMapIter_t; + typedef StatMap_t::value_type StatMapEntry_t; + + typedef std::set > Filenames_t; + + // Heap reporting method + typedef void (*HeapReportFunc_t)( char const *pFormat, ... ); + +private: + // Returns the actual debug info + void GetActualDbgInfo( const char *&pFileName, int &nLine ); + + void Initialize(); + + // Finds the file in our map + MemInfo_t &FindOrCreateEntry( const char *pFileName, int line ); + const char *FindOrCreateFilename( const char *pFileName ); + + // Updates stats + void RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ); + void RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ); + + void RegisterAllocation( MemInfo_t &info, int nLogicalSize, int nActualSize, unsigned nTime ); + void RegisterDeallocation( MemInfo_t &info, int nLogicalSize, int nActualSize, unsigned nTime ); + + // Gets the allocation file name + const char *GetAllocatonFileName( void *pMem ); + int GetAllocatonLineNumber( void *pMem ); + + // FIXME: specify a spew output func for dumping stats + // Stat output + void DumpMemInfo( const char *pAllocationName, int line, const MemInfo_t &info ); + void DumpFileStats(); + void DumpStats(); + void DumpStatsFileBase( char const *pchFileBase ); + void DumpBlockStats( void *p ); + virtual void GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ); + +private: + StatMap_t *m_pStatMap; + MemInfo_t m_GlobalInfo; + CFastTimer m_Timer; + bool m_bInitialized; + Filenames_t *m_pFilenames; + + HeapReportFunc_t m_OutputFunc; + + static int s_pCountSizes[NUM_BYTE_COUNT_BUCKETS]; + static const char *s_pCountHeader[NUM_BYTE_COUNT_BUCKETS]; + + size_t m_sMemoryAllocFailed; +}; + +static char const *g_pszUnknown = "unknown"; + +//----------------------------------------------------------------------------- + +const int DBG_INFO_STACK_DEPTH = 32; + +struct DbgInfoStack_t +{ + const char *m_pFileName; + int m_nLine; +}; + +CThreadLocalPtr g_DbgInfoStack CONSTRUCT_EARLY; +CThreadLocalInt<> g_nDbgInfoStackDepth CONSTRUCT_EARLY; + +//----------------------------------------------------------------------------- +// Singleton... +//----------------------------------------------------------------------------- +static CDbgMemAlloc s_DbgMemAlloc CONSTRUCT_EARLY; + +#ifndef TIER0_VALIDATE_HEAP +IMemAlloc *g_pMemAlloc = &s_DbgMemAlloc; +#else +IMemAlloc *g_pActualAlloc = &s_DbgMemAlloc; +#endif + +//----------------------------------------------------------------------------- + +CThreadMutex g_DbgMemMutex CONSTRUCT_EARLY; + +#define HEAP_LOCK() AUTO_LOCK( g_DbgMemMutex ) + + +//----------------------------------------------------------------------------- +// Byte count buckets +//----------------------------------------------------------------------------- +int CDbgMemAlloc::s_pCountSizes[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] = +{ + 16, 32, 128, 1024, INT_MAX +}; + +const char *CDbgMemAlloc::s_pCountHeader[CDbgMemAlloc::NUM_BYTE_COUNT_BUCKETS] = +{ + "<=16 byte allocations", + "17-32 byte allocations", + "33-128 byte allocations", + "129-1024 byte allocations", + ">1024 byte allocations" +}; + +//----------------------------------------------------------------------------- +// Standard output +//----------------------------------------------------------------------------- +static FILE* s_DbgFile; + +static void DefaultHeapReportFunc( char const *pFormat, ... ) +{ + va_list args; + va_start( args, pFormat ); + vfprintf( s_DbgFile, pFormat, args ); + va_end( args ); +} + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CDbgMemAlloc::CDbgMemAlloc() : m_sMemoryAllocFailed( (size_t)0 ) +{ + // Make sure that we return 64-bit addresses in 64-bit builds. + ReserveBottomMemory(); + + m_OutputFunc = DefaultHeapReportFunc; + m_bInitialized = false; + + if ( !IsDebug() && !IsX360() ) + { + Plat_DebugString( "USE_MEM_DEBUG is enabled in a release build. Don't check this in!\n" ); + } +} + +CDbgMemAlloc::~CDbgMemAlloc() +{ + Shutdown(); +} + + +void CDbgMemAlloc::Initialize() +{ + if ( !m_bInitialized ) + { + m_pFilenames = new Filenames_t; + m_pStatMap= new StatMap_t; + m_bInitialized = true; + } +} + + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- +void CDbgMemAlloc::Shutdown() +{ + if ( m_bInitialized ) + { + Filenames_t::const_iterator iter = m_pFilenames->begin(); + while ( iter != m_pFilenames->end() ) + { + char *pFileName = (char*)(*iter); + free( pFileName ); + iter++; + } + m_pFilenames->clear(); + + m_bInitialized = false; + + delete m_pFilenames; + m_pFilenames = nullptr; + + delete m_pStatMap; + m_pStatMap = nullptr; + } + + m_bInitialized = false; +} + + +#ifdef WIN32 +extern "C" BOOL APIENTRY MemDbgDllMain( HMODULE hDll, DWORD dwReason, PVOID pvReserved ) +{ + UNREFERENCED_PARAMETER( pvReserved ); + + // Check if we are shutting down + if ( dwReason == DLL_PROCESS_DETACH ) + { + // CDbgMemAlloc is a global object and destructs after the _Lockit object in the CRT runtime, + // so we can't actually operate on the STL object in a normal destructor here as its support libraries have been turned off already + s_DbgMemAlloc.Shutdown(); + } + + return TRUE; +} +#endif + + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- + +void *CDbgMemAlloc::Alloc( size_t nSize ) +{ +/* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } +*/ + char szModule[MAX_PATH]; + if ( GetCallerModule( szModule ) ) + { + return Alloc( nSize, szModule, 0 ); + } + else + { + return Alloc( nSize, g_pszUnknown, 0 ); + } +// return malloc( nSize ); +} + +void *CDbgMemAlloc::Realloc( void *pMem, size_t nSize ) +{ +/* + // NOTE: Uncomment this to find unknown allocations + const char *pFileName = g_pszUnknown; + int nLine; + GetActualDbgInfo( pFileName, nLine ); + if (pFileName == g_pszUnknown) + { + int x = 3; + } +*/ + // FIXME: Should these gather stats? + char szModule[MAX_PATH]; + if ( GetCallerModule( szModule ) ) + { + return Realloc( pMem, nSize, szModule, 0 ); + } + else + { + return Realloc( pMem, nSize, g_pszUnknown, 0 ); + } +// return realloc( pMem, nSize ); +} + +void CDbgMemAlloc::Free( void *pMem ) +{ + // FIXME: Should these gather stats? + Free( pMem, g_pszUnknown, 0 ); +// free( pMem ); +} + +void *CDbgMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize ) +{ + return NULL; +} + + +//----------------------------------------------------------------------------- +// Force file + line information for an allocation +//----------------------------------------------------------------------------- +void CDbgMemAlloc::PushAllocDbgInfo( const char *pFileName, int nLine ) +{ + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + ++g_nDbgInfoStackDepth; + Assert( g_nDbgInfoStackDepth < DBG_INFO_STACK_DEPTH ); + g_DbgInfoStack[g_nDbgInfoStackDepth].m_pFileName = FindOrCreateFilename( pFileName ); + g_DbgInfoStack[g_nDbgInfoStackDepth].m_nLine = nLine; +} + +void CDbgMemAlloc::PopAllocDbgInfo() +{ + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + --g_nDbgInfoStackDepth; + Assert( g_nDbgInfoStackDepth >= -1 ); +} + + +//----------------------------------------------------------------------------- +// handles storing allocation info for coroutines +//----------------------------------------------------------------------------- +uint32 CDbgMemAlloc::GetDebugInfoSize() +{ + return sizeof( DbgInfoStack_t ) * DBG_INFO_STACK_DEPTH + sizeof( int32 ); +} + +void CDbgMemAlloc::SaveDebugInfo( void *pvDebugInfo ) +{ + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + int32 *pnStackDepth = (int32*) pvDebugInfo; + *pnStackDepth = g_nDbgInfoStackDepth; + memcpy( pnStackDepth+1, &g_DbgInfoStack[0], sizeof( DbgInfoStack_t ) * DBG_INFO_STACK_DEPTH ); +} + +void CDbgMemAlloc::RestoreDebugInfo( const void *pvDebugInfo ) +{ + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + const int32 *pnStackDepth = (const int32*) pvDebugInfo; + g_nDbgInfoStackDepth = *pnStackDepth; + memcpy( &g_DbgInfoStack[0], pnStackDepth+1, sizeof( DbgInfoStack_t ) * DBG_INFO_STACK_DEPTH ); + +} + +void CDbgMemAlloc::InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) +{ + int32 *pnStackDepth = (int32*) pvDebugInfo; + + if( pchRootFileName ) + { + *pnStackDepth = 0; + + DbgInfoStack_t *pStackRoot = (DbgInfoStack_t *)(pnStackDepth + 1); + pStackRoot->m_pFileName = FindOrCreateFilename( pchRootFileName ); + pStackRoot->m_nLine = nLine; + } + else + { + *pnStackDepth = -1; + } + +} + + +//----------------------------------------------------------------------------- +// Returns the actual debug info +//----------------------------------------------------------------------------- +void CDbgMemAlloc::GetActualDbgInfo( const char *&pFileName, int &nLine ) +{ +#if defined( USE_STACK_WALK_DETAILED ) + return; +#endif + + if ( g_DbgInfoStack == NULL ) + { + g_DbgInfoStack = (DbgInfoStack_t *)DebugAlloc( sizeof(DbgInfoStack_t) * DBG_INFO_STACK_DEPTH ); + g_nDbgInfoStackDepth = -1; + } + + if ( g_nDbgInfoStackDepth >= 0 && g_DbgInfoStack[0].m_pFileName) + { + pFileName = g_DbgInfoStack[0].m_pFileName; + nLine = g_DbgInfoStack[0].m_nLine; + } +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +const char *CDbgMemAlloc::FindOrCreateFilename( const char *pFileName ) +{ + Initialize(); + + // If we created it for the first time, actually *allocate* the filename memory + HEAP_LOCK(); + // This is necessary for shutdown conditions: the file name is stored + // in some piece of memory in a DLL; if that DLL becomes unloaded, + // we'll have a pointer to crap memory + + if ( !pFileName ) + { + pFileName = g_pszUnknown; + } + +#if defined( USE_STACK_WALK_DETAILED ) +{ + + // Walk the stack to determine what's causing the allocation + void *arrStackAddresses[ 10 ] = { 0 }; + int numStackAddrRetrieved = WalkStack( arrStackAddresses, 10, 0 ); + char *szStack = StackDescribe( arrStackAddresses, numStackAddrRetrieved ); + if ( szStack && *szStack ) + { + pFileName = szStack; // Use the stack description for the allocation + } + +} +#endif // #if defined( USE_STACK_WALK_DETAILED ) + + char *pszFilenameCopy; + Filenames_t::const_iterator iter = m_pFilenames->find( pFileName ); + if ( iter == m_pFilenames->end() ) + { + int nLen = strlen(pFileName) + 1; + pszFilenameCopy = (char *)DebugAlloc( nLen ); + memcpy( pszFilenameCopy, pFileName, nLen ); + m_pFilenames->insert( pszFilenameCopy ); + } + else + { + pszFilenameCopy = (char *)(*iter); + } + + return pszFilenameCopy; +} + +//----------------------------------------------------------------------------- +// Finds the file in our map +//----------------------------------------------------------------------------- +CDbgMemAlloc::MemInfo_t &CDbgMemAlloc::FindOrCreateEntry( const char *pFileName, int line ) +{ + Initialize(); + // Oh how I love crazy STL. retval.first == the StatMapIter_t in the std::pair + // retval.first->second == the MemInfo_t that's part of the StatMapIter_t + std::pair retval; + if ( m_pStatMap ) + { + retval = m_pStatMap->insert( StatMapEntry_t( MemInfoKey_t( pFileName, line ), MemInfo_t() ) ); + } + return retval.first->second; +} + + +//----------------------------------------------------------------------------- +// Updates stats +//----------------------------------------------------------------------------- +void CDbgMemAlloc::RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + HEAP_LOCK(); + RegisterAllocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); + RegisterAllocation( FindOrCreateEntry( pFileName, nLine ), nLogicalSize, nActualSize, nTime ); +} + +void CDbgMemAlloc::RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + HEAP_LOCK(); + RegisterDeallocation( m_GlobalInfo, nLogicalSize, nActualSize, nTime ); + RegisterDeallocation( FindOrCreateEntry( pFileName, nLine ), nLogicalSize, nActualSize, nTime ); +} + +void CDbgMemAlloc::RegisterAllocation( MemInfo_t &info, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + ++info.m_nCurrentCount; + ++info.m_nTotalCount; + if (info.m_nCurrentCount > info.m_nPeakCount) + { + info.m_nPeakCount = info.m_nCurrentCount; + } + + info.m_nCurrentSize += nLogicalSize; + info.m_nTotalSize += nLogicalSize; + if (info.m_nCurrentSize > info.m_nPeakSize) + { + info.m_nPeakSize = info.m_nCurrentSize; + } + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) + { + if (nLogicalSize <= s_pCountSizes[i]) + { + ++info.m_pCount[i]; + break; + } + } + + Assert( info.m_nPeakCount >= info.m_nCurrentCount ); + Assert( info.m_nPeakSize >= info.m_nCurrentSize ); + + info.m_nOverheadSize += (nActualSize - nLogicalSize); + if (info.m_nOverheadSize > info.m_nPeakOverheadSize) + { + info.m_nPeakOverheadSize = info.m_nOverheadSize; + } + + info.m_nTime += nTime; +} + +void CDbgMemAlloc::RegisterDeallocation( MemInfo_t &info, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + // Check for decrementing these counters below zero. The checks + // must be done here because these unsigned counters will wrap-around and + // still be positive. + Assert( info.m_nCurrentCount != 0 ); + + // It is technically legal for code to request allocations of zero bytes, and there are a number of places in our code + // that do. So only assert that nLogicalSize >= 0. http://stackoverflow.com/questions/1087042/c-new-int0-will-it-allocate-memory + Assert( nLogicalSize >= 0 ); + Assert( info.m_nCurrentSize >= (size_t)nLogicalSize ); + --info.m_nCurrentCount; + info.m_nCurrentSize -= nLogicalSize; + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) + { + if (nLogicalSize <= s_pCountSizes[i]) + { + --info.m_pCount[i]; + break; + } + } + + Assert( info.m_nPeakCount >= info.m_nCurrentCount ); + Assert( info.m_nPeakSize >= info.m_nCurrentSize ); + + info.m_nOverheadSize -= (nActualSize - nLogicalSize); + + info.m_nTime += nTime; +} + + +//----------------------------------------------------------------------------- +// Gets the allocation file name +//----------------------------------------------------------------------------- + +const char *CDbgMemAlloc::GetAllocatonFileName( void *pMem ) +{ + if (!pMem) + return ""; + + CrtDbgMemHeader_t *pHeader = GetCrtDbgMemHeader( pMem ); + if ( pHeader->m_pFileName ) + return pHeader->m_pFileName; + else + return g_pszUnknown; +} + +//----------------------------------------------------------------------------- +// Gets the allocation file name +//----------------------------------------------------------------------------- +int CDbgMemAlloc::GetAllocatonLineNumber( void *pMem ) +{ + if ( !pMem ) + return 0; + + CrtDbgMemHeader_t *pHeader = GetCrtDbgMemHeader( pMem ); + return pHeader->m_nLineNumber; +} + +//----------------------------------------------------------------------------- +// Debug versions of the main allocation methods +//----------------------------------------------------------------------------- +void *CDbgMemAlloc::Alloc( size_t nSize, const char *pFileName, int nLine ) +{ + HEAP_LOCK(); + + if ( !m_bInitialized ) + return InternalMalloc( nSize, pFileName, nLine ); + + if ( pFileName != g_pszUnknown ) + pFileName = FindOrCreateFilename( pFileName ); + + GetActualDbgInfo( pFileName, nLine ); + + /* + if ( strcmp( pFileName, "class CUtlVector >" ) == 0) + { + GetActualDbgInfo( pFileName, nLine ); + } + */ + + m_Timer.Start(); + void *pMem = InternalMalloc( nSize, pFileName, nLine ); + m_Timer.End(); + + ApplyMemoryInitializations( pMem, nSize ); + + if ( pMem ) + { + RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem ), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); + } + else + { + SetCRTAllocFailed( nSize ); + } + return pMem; +} + +void *CDbgMemAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + HEAP_LOCK(); + + pFileName = FindOrCreateFilename( pFileName ); + + if ( !m_bInitialized ) + return InternalRealloc( pMem, nSize, pFileName, nLine ); + + if ( pMem != 0 ) + { + RegisterDeallocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem), InternalMSize( pMem ), 0 ); + } + + GetActualDbgInfo( pFileName, nLine ); + + m_Timer.Start(); + pMem = InternalRealloc( pMem, nSize, pFileName, nLine ); + m_Timer.End(); + + if ( pMem ) + { + RegisterAllocation( GetAllocatonFileName( pMem ), GetAllocatonLineNumber( pMem ), InternalLogicalSize( pMem), InternalMSize( pMem ), m_Timer.GetDuration().GetMicroseconds() ); + } + else + { + SetCRTAllocFailed( nSize ); + } + return pMem; +} + +void CDbgMemAlloc::Free( void *pMem, const char * /*pFileName*/, int nLine ) +{ + if ( !pMem ) + return; + + HEAP_LOCK(); + + if ( !m_bInitialized ) + { + InternalFree( pMem ); + return; + } + + int nOldLogicalSize = InternalLogicalSize( pMem ); + int nOldSize = InternalMSize( pMem ); + const char *pOldFileName = GetAllocatonFileName( pMem ); + int oldLine = GetAllocatonLineNumber( pMem ); + + m_Timer.Start(); + InternalFree( pMem ); + m_Timer.End(); + + RegisterDeallocation( pOldFileName, oldLine, nOldLogicalSize, nOldSize, m_Timer.GetDuration().GetMicroseconds() ); +} + +void *CDbgMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + return NULL; +} + + +//----------------------------------------------------------------------------- +// Returns size of a particular allocation +//----------------------------------------------------------------------------- +size_t CDbgMemAlloc::GetSize( void *pMem ) +{ + HEAP_LOCK(); + + if ( !pMem ) + return CalcHeapUsed(); + + return InternalMSize( pMem ); +} + + +//----------------------------------------------------------------------------- +// FIXME: Remove when we make our own heap! Crt stuff we're currently using +//----------------------------------------------------------------------------- +long CDbgMemAlloc::CrtSetBreakAlloc( long lNewBreakAlloc ) +{ +#ifdef POSIX + return 0; +#else + return _CrtSetBreakAlloc( lNewBreakAlloc ); +#endif +} + +int CDbgMemAlloc::CrtSetReportMode( int nReportType, int nReportMode ) +{ +#ifdef POSIX + return 0; +#else + return _CrtSetReportMode( nReportType, nReportMode ); +#endif +} + +int CDbgMemAlloc::CrtIsValidHeapPointer( const void *pMem ) +{ +#ifdef POSIX + return 0; +#else + return _CrtIsValidHeapPointer( pMem ); +#endif +} + +int CDbgMemAlloc::CrtIsValidPointer( const void *pMem, unsigned int size, int access ) +{ +#ifdef POSIX + return 0; +#else + return _CrtIsValidPointer( pMem, size, access ); +#endif +} + +#define DBGMEM_CHECKMEMORY 1 + +int CDbgMemAlloc::CrtCheckMemory( void ) +{ +#if !defined( DBGMEM_CHECKMEMORY ) || defined( POSIX ) + return 1; +#else + if ( !_CrtCheckMemory()) + { + Msg( "Memory check failed!\n" ); + return 0; + } + return 1; +#endif +} + +int CDbgMemAlloc::CrtSetDbgFlag( int nNewFlag ) +{ +#ifdef POSIX + return 0; +#else + return _CrtSetDbgFlag( nNewFlag ); +#endif +} + +void CDbgMemAlloc::CrtMemCheckpoint( _CrtMemState *pState ) +{ +#ifndef POSIX + _CrtMemCheckpoint( pState ); +#endif +} + +// FIXME: Remove when we have our own allocator +void* CDbgMemAlloc::CrtSetReportFile( int nRptType, void* hFile ) +{ +#ifdef POSIX + return 0; +#else + return (void*)_CrtSetReportFile( nRptType, (_HFILE)hFile ); +#endif +} + +void* CDbgMemAlloc::CrtSetReportHook( void* pfnNewHook ) +{ +#ifdef POSIX + return 0; +#else + return (void*)_CrtSetReportHook( (_CRT_REPORT_HOOK)pfnNewHook ); +#endif +} + +int CDbgMemAlloc::CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ) +{ +#ifdef POSIX + return 0; +#else + return _CrtDbgReport( nRptType, szFile, nLine, szModule, pMsg ); +#endif +} + +int CDbgMemAlloc::heapchk() +{ +#ifdef POSIX + return 0; +#else + return _HEAPOK; +#endif +} + +void CDbgMemAlloc::DumpBlockStats( void *p ) +{ + DbgMemHeader_t *pBlock = (DbgMemHeader_t *)p - 1; + if ( !CrtIsValidHeapPointer( pBlock ) ) + { + Msg( "0x%p is not valid heap pointer\n", p ); + return; + } + + const char *pFileName = GetAllocatonFileName( p ); + int line = GetAllocatonLineNumber( p ); + + Msg( "0x%p allocated by %s line %d, %llu bytes\n", p, pFileName, line, (uint64)GetSize( p ) ); +} + +//----------------------------------------------------------------------------- +// Stat output +//----------------------------------------------------------------------------- +void CDbgMemAlloc::DumpMemInfo( const char *pAllocationName, int line, const MemInfo_t &info ) +{ + m_OutputFunc("%s, line %i\t%.1f\t%.1f\t%.1f\t%.1f\t%.1f\t%d\t%d\t%d\t%d", + pAllocationName, + line, + info.m_nCurrentSize / 1024.0f, + info.m_nPeakSize / 1024.0f, + info.m_nTotalSize / 1024.0f, + info.m_nOverheadSize / 1024.0f, + info.m_nPeakOverheadSize / 1024.0f, + (int)(info.m_nTime / 1000), + info.m_nCurrentCount, + info.m_nPeakCount, + info.m_nTotalCount + ); + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) + { + m_OutputFunc( "\t%d", info.m_pCount[i] ); + } + + m_OutputFunc("\n"); +} + + +//----------------------------------------------------------------------------- +// Stat output +//----------------------------------------------------------------------------- +void CDbgMemAlloc::DumpFileStats() +{ + if ( !m_pStatMap ) + return; + + StatMapIter_t iter = m_pStatMap->begin(); + while ( iter != m_pStatMap->end() ) + { + DumpMemInfo( iter->first.m_pFileName, iter->first.m_nLine, iter->second ); + iter++; + } +} + +void CDbgMemAlloc::DumpStatsFileBase( char const *pchFileBase ) +{ + HEAP_LOCK(); + + char szFileName[MAX_PATH]; + static int s_FileCount = 0; + if (m_OutputFunc == DefaultHeapReportFunc) + { + char *pPath = ""; + if ( IsX360() ) + { + pPath = "D:\\"; + } + +#if defined( _MEMTEST ) && defined( _X360 ) + char szXboxName[32]; + strcpy( szXboxName, "xbox" ); + DWORD numChars = sizeof( szXboxName ); + DmGetXboxName( szXboxName, &numChars ); + char *pXboxName = strstr( szXboxName, "_360" ); + if ( pXboxName ) + { + *pXboxName = '\0'; + } + + SYSTEMTIME systemTime; + GetLocalTime( &systemTime ); + _snprintf( szFileName, sizeof( szFileName ), "%s%s_%2.2d%2.2d_%2.2d%2.2d%2.2d_%d.txt", pPath, s_szStatsMapName, systemTime.wMonth, systemTime.wDay, systemTime.wHour, systemTime.wMinute, systemTime.wSecond, s_FileCount ); +#else + _snprintf( szFileName, sizeof( szFileName ), "%s%s%d.txt", pPath, pchFileBase, s_FileCount ); +#endif + szFileName[ ARRAYSIZE(szFileName) - 1 ] = 0; + + ++s_FileCount; + + s_DbgFile = fopen(szFileName, "wt"); + if (!s_DbgFile) + return; + } + + m_OutputFunc("Allocation type\tCurrent Size(k)\tPeak Size(k)\tTotal Allocations(k)\tOverhead Size(k)\tPeak Overhead Size(k)\tTime(ms)\tCurrent Count\tPeak Count\tTotal Count"); + + for (int i = 0; i < NUM_BYTE_COUNT_BUCKETS; ++i) + { + m_OutputFunc( "\t%s", s_pCountHeader[i] ); + } + + m_OutputFunc("\n"); + + DumpMemInfo( "Totals", 0, m_GlobalInfo ); + +#ifdef WIN32 + if ( IsX360() ) + { + // add a line that has free memory + size_t usedMemory, freeMemory; + GlobalMemoryStatus( &usedMemory, &freeMemory ); + MemInfo_t info; + // OS takes 32 MB, report our internal allocations only + info.m_nCurrentSize = usedMemory; + DumpMemInfo( "Used Memory", 0, info ); + } +#endif + + DumpFileStats(); + + if (m_OutputFunc == DefaultHeapReportFunc) + { + fclose(s_DbgFile); + +#if defined( _X360 ) && !defined( _RETAIL ) + XBX_rMemDump( szFileName ); +#endif + } +} + +void CDbgMemAlloc::GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ) +{ + if ( !pUsedMemory || !pFreeMemory ) + return; + +#if defined ( _X360 ) + + // GlobalMemoryStatus tells us how much physical memory is free + MEMORYSTATUS stat; + ::GlobalMemoryStatus( &stat ); + *pFreeMemory = stat.dwAvailPhys; + + // Used is total minus free (discount the 32MB system reservation) + *pUsedMemory = ( stat.dwTotalPhys - 32*1024*1024 ) - *pFreeMemory; + +#else + + // no data + *pFreeMemory = 0; + *pUsedMemory = 0; + +#endif +} + +//----------------------------------------------------------------------------- +// Stat output +//----------------------------------------------------------------------------- +void CDbgMemAlloc::DumpStats() +{ + DumpStatsFileBase( "memstats" ); +} + +void CDbgMemAlloc::SetCRTAllocFailed( size_t nSize ) +{ + m_sMemoryAllocFailed = nSize; + + MemAllocOOMError( nSize ); +} + +size_t CDbgMemAlloc::MemoryAllocFailed() +{ + return m_sMemoryAllocFailed; +} + + + +#if defined( LINUX ) && !defined( NO_HOOK_MALLOC ) +// +// Under linux we can ask GLIBC to override malloc for us +// Base on code from Ryan, http://hg.icculus.org/icculus/mallocmonitor/file/29c4b0d049f7/monitor_client/malloc_hook_glibc.c +// +// +static void *glibc_malloc_hook = NULL; +static void *glibc_realloc_hook = NULL; +static void *glibc_memalign_hook = NULL; +static void *glibc_free_hook = NULL; + +/* convenience functions for setting the hooks... */ +static inline void save_glibc_hooks(void); +static inline void set_glibc_hooks(void); +static inline void set_override_hooks(void); + +CThreadMutex g_HookMutex; +/* + * Our overriding hooks...they call through to the original C runtime + * implementations and report to the monitoring daemon. + */ + +static void *override_malloc_hook(size_t s, const void *caller) +{ + void *retval; + AUTO_LOCK( g_HookMutex ); + set_glibc_hooks(); /* put glibc back in control. */ + retval = InternalMalloc( s, NULL, 0 ); + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ + + return(retval); +} /* override_malloc_hook */ + + +static void *override_realloc_hook(void *ptr, size_t s, const void *caller) +{ + void *retval; + AUTO_LOCK( g_HookMutex ); + + set_glibc_hooks(); /* put glibc back in control. */ + retval = InternalRealloc(ptr, s, NULL, 0); /* call glibc version. */ + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ + + return(retval); +} /* override_realloc_hook */ + + +static void *override_memalign_hook(size_t a, size_t s, const void *caller) +{ + void *retval; + AUTO_LOCK( g_HookMutex ); + + set_glibc_hooks(); /* put glibc back in control. */ + retval = memalign(a, s); /* call glibc version. */ + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ + + return(retval); +} /* override_memalign_hook */ + + +static void override_free_hook(void *ptr, const void *caller) +{ + AUTO_LOCK( g_HookMutex ); + + set_glibc_hooks(); /* put glibc back in control. */ + InternalFree(ptr); /* call glibc version. */ + save_glibc_hooks(); /* update in case glibc changed them. */ + + set_override_hooks(); /* only restore hooks if daemon is listening */ +} /* override_free_hook */ + + + +/* + * Convenience functions for swapping the hooks around... + */ + +/* + * Save a copy of the original allocation hooks, so we can call into them + * from our overriding functions. It's possible that glibc might change + * these hooks under various conditions (so the manual's examples seem + * to suggest), so we update them whenever we finish calling into the + * the originals. + */ +static inline void save_glibc_hooks(void) +{ + glibc_malloc_hook = (void *)__malloc_hook; + glibc_realloc_hook = (void *)__realloc_hook; + glibc_memalign_hook = (void *)__memalign_hook; + glibc_free_hook = (void *)__free_hook; +} /* save_glibc_hooks */ + +/* + * Restore the hooks to the glibc versions. This is needed since, say, + * their realloc() might call malloc() or free() under the hood, etc, so + * it's safer to let them have complete control over the subsystem, which + * also makes our logging saner, too. + */ +static inline void set_glibc_hooks(void) +{ + __malloc_hook = (void* (*)(size_t, const void*))glibc_malloc_hook; + __realloc_hook = (void* (*)(void*, size_t, const void*))glibc_realloc_hook; + __memalign_hook = (void* (*)(size_t, size_t, const void*))glibc_memalign_hook; + __free_hook = (void (*)(void*, const void*))glibc_free_hook; +} /* set_glibc_hooks */ + + +/* + * Put our hooks back in place. This should be done after the original + * glibc version has been called and we've finished any logging (which + * may call glibc functions, too). This sets us up for the next calls from + * the application. + */ +static inline void set_override_hooks(void) +{ + __malloc_hook = override_malloc_hook; + __realloc_hook = override_realloc_hook; + __memalign_hook = override_memalign_hook; + __free_hook = override_free_hook; +} /* set_override_hooks */ + + + +/* + * The Hook Of All Hooks...how we get in there in the first place. + */ + +/* + * glibc will call this when the malloc subsystem is initializing, giving + * us a chance to install hooks that override the functions. + */ +static void __attribute__((constructor)) override_init_hook(void) +{ + AUTO_LOCK( g_HookMutex ); + + /* install our hooks. Will connect to daemon on first malloc, etc. */ + save_glibc_hooks(); + set_override_hooks(); +} /* override_init_hook */ + + +/* + * __malloc_initialize_hook is apparently a "weak variable", so you can + * define and assign it here even though it's in glibc, too. This lets + * us hook into malloc as soon as the runtime initializes, and before + * main() is called. Basically, this whole trick depends on this. + */ +void (*__MALLOC_HOOK_VOLATILE __malloc_initialize_hook)(void) __attribute__((visibility("default")))= override_init_hook; + +#endif // LINUX + + +#if defined( OSX ) && !defined( NO_HOOK_MALLOC ) +// +// pointers to the osx versions of these functions +static void *osx_malloc_hook = NULL; +static void *osx_realloc_hook = NULL; +static void *osx_free_hook = NULL; + +// convenience functions for setting the hooks... +static inline void save_osx_hooks(void); +static inline void set_osx_hooks(void); +static inline void set_override_hooks(void); + +CThreadMutex g_HookMutex; +// +// Our overriding hooks...they call through to the original C runtime +// implementations and report to the monitoring daemon. +// + +static void *override_malloc_hook(struct _malloc_zone_t *zone, size_t s) +{ + void *retval; + set_osx_hooks(); + retval = InternalMalloc( s, NULL, 0 ); + set_override_hooks(); + + return(retval); +} + + +static void *override_realloc_hook(struct _malloc_zone_t *zone, void *ptr, size_t s) +{ + void *retval; + + set_osx_hooks(); + retval = InternalRealloc(ptr, s, NULL, 0); + set_override_hooks(); + + return(retval); +} + + +static void override_free_hook(struct _malloc_zone_t *zone, void *ptr) +{ + // sometime they pass in a null pointer from higher level calls, just ignore it + if ( !ptr ) + return; + + set_osx_hooks(); + + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( ptr ); + if ( *((int*)pInternalMem->m_Reserved) == 0xf00df00d ) + { + InternalFree( ptr ); + } + + set_override_hooks(); +} + + +/* + + These are func's we could optionally override right now on OSX but don't need to + + static size_t override_size_hook(struct _malloc_zone_t *zone, const void *ptr) + { + set_osx_hooks(); + DbgMemHeader_t *pInternalMem = GetCrtDbgMemHeader( (void *)ptr ); + set_override_hooks(); + if ( *((int*)pInternalMem->m_Reserved) == 0xf00df00d ) + { + return pInternalMem->nLogicalSize; + } + return 0; + } + + + static void *override_calloc_hook(struct _malloc_zone_t *zone, size_t num_items, size_t size ) + { + void *ans = override_malloc_hook( zone, num_items*size ); + if ( !ans ) + return 0; + memset( ans, 0x0, num_items*size ); + return ans; + } + + static void *override_valloc_hook(struct _malloc_zone_t *zone, size_t size ) + { + return override_calloc_hook( zone, 1, size ); + } + + static void override_destroy_hook(struct _malloc_zone_t *zone) + { + } + */ + + +static inline void unprotect_malloc_zone( malloc_zone_t *malloc_zone ) +{ + // Starting in OS X 10.7 the default zone defaults to read-only, version 8. + // The version check may not be necessary, but we know it was RW before that. + if ( malloc_zone->version >= 8 ) + { + vm_protect( mach_task_self(), (uintptr_t)malloc_zone, sizeof( malloc_zone_t ), 0, VM_PROT_READ | VM_PROT_WRITE ); + } +} + +static inline void protect_malloc_zone( malloc_zone_t *malloc_zone ) +{ + if ( malloc_zone->version >= 8 ) + { + vm_protect( mach_task_self(), (uintptr_t)malloc_zone, sizeof( malloc_zone_t ), 0, VM_PROT_READ ); + } +} + +// +// Save a copy of the original allocation hooks, so we can call into them +// from our overriding functions. It's possible that osx might change +// these hooks under various conditions (so the manual's examples seem +// to suggest), so we update them whenever we finish calling into the +// the originals. +// +static inline void save_osx_hooks(void) +{ + malloc_zone_t *malloc_zone = malloc_default_zone(); + + osx_malloc_hook = (void *)malloc_zone->malloc; + osx_realloc_hook = (void *)malloc_zone->realloc; + osx_free_hook = (void *)malloc_zone->free; + + // These are func's we could optionally override right now on OSX but don't need to + // osx_size_hook = (void *)malloc_zone->size; + // osx_calloc_hook = (void *)malloc_zone->calloc; + // osx_valloc_hook = (void *)malloc_zone->valloc; + // osx_destroy_hook = (void *)malloc_zone->destroy; +} + +// +// Restore the hooks to the osx versions. This is needed since, say, +// their realloc() might call malloc() or free() under the hood, etc, so +// it's safer to let them have complete control over the subsystem, which +// also makes our logging saner, too. +// +static inline void set_osx_hooks(void) +{ + malloc_zone_t *malloc_zone = malloc_default_zone(); + + unprotect_malloc_zone( malloc_zone ); + malloc_zone->malloc = (void* (*)(_malloc_zone_t*, size_t))osx_malloc_hook; + malloc_zone->realloc = (void* (*)(_malloc_zone_t*, void*, size_t))osx_realloc_hook; + malloc_zone->free = (void (*)(_malloc_zone_t*, void*))osx_free_hook; + protect_malloc_zone( malloc_zone ); + + // These are func's we could optionally override right now on OSX but don't need to + + //malloc_zone->size = (size_t (*)(_malloc_zone_t*, const void *))osx_size_hook; + //malloc_zone->calloc = (void* (*)(_malloc_zone_t*, size_t, size_t))osx_calloc_hook; + //malloc_zone->valloc = (void* (*)(_malloc_zone_t*, size_t))osx_valloc_hook; + //malloc_zone->destroy = (void (*)(_malloc_zone_t*))osx_destroy_hook; +} + + +/* + * Put our hooks back in place. This should be done after the original + * osx version has been called and we've finished any logging (which + * may call osx functions, too). This sets us up for the next calls from + * the application. + */ +static inline void set_override_hooks(void) +{ + malloc_zone_t *malloc_zone = malloc_default_zone(); + AssertMsg( malloc_zone, "No malloc zone returned by malloc_default_zone" ); + + unprotect_malloc_zone( malloc_zone ); + malloc_zone->malloc = override_malloc_hook; + malloc_zone->realloc = override_realloc_hook; + malloc_zone->free = override_free_hook; + protect_malloc_zone( malloc_zone ); + + // These are func's we could optionally override right now on OSX but don't need to + //malloc_zone->size = override_size_hook; + //malloc_zone->calloc = override_calloc_hook; + // malloc_zone->valloc = override_valloc_hook; + //malloc_zone->destroy = override_destroy_hook; +} + + +// +// The Hook Of All Hooks...how we get in there in the first place. +// +// osx will call this when the malloc subsystem is initializing, giving +// us a chance to install hooks that override the functions. +// + +void __attribute__ ((constructor)) mem_init(void) +{ + AUTO_LOCK( g_HookMutex ); + save_osx_hooks(); + set_override_hooks(); +} + +void *operator new( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + set_osx_hooks(); + void *pMem = g_pMemAlloc->Alloc(nSize, pFileName, nLine); + set_override_hooks(); + return pMem; +} + +void *operator new[] ( size_t nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + set_osx_hooks(); + void *pMem = g_pMemAlloc->Alloc(nSize, pFileName, nLine); + set_override_hooks(); + return pMem; +} + + +#endif // defined( OSX ) && !defined( NO_HOOK_MALLOC ) + + +#endif // (defined(_DEBUG) || defined(USE_MEM_DEBUG)) + +#endif // !STEAM && !NO_MALLOC_OVERRIDE diff --git a/tier0/meminit.cpp b/tier0/meminit.cpp new file mode 100644 index 0000000..d44d97d --- /dev/null +++ b/tier0/meminit.cpp @@ -0,0 +1,9 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//=============================================================================// + + +#include "pch_tier0.h" diff --git a/tier0/memstd.cpp b/tier0/memstd.cpp new file mode 100644 index 0000000..0504fdc --- /dev/null +++ b/tier0/memstd.cpp @@ -0,0 +1,1900 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN_32_LEAN_AND_MEAN +#include +#define VA_COMMIT_FLAGS MEM_COMMIT +#define VA_RESERVE_FLAGS MEM_RESERVE +#elif defined( _X360 ) +#undef Verify +#define VA_COMMIT_FLAGS (MEM_COMMIT|MEM_NOZERO|MEM_LARGE_PAGES) +#define VA_RESERVE_FLAGS (MEM_RESERVE|MEM_LARGE_PAGES) +#endif + +#include + +#include "tier0/valve_minmax_off.h" // GCC 4.2.2 headers screw up our min/max defs. +#include +#include "tier0/valve_minmax_on.h" // GCC 4.2.2 headers screw up our min/max defs. + +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "tier0/threadtools.h" +#include "mem_helpers.h" +#include "memstd.h" +#ifdef _X360 +#include "xbox/xbox_console.h" +#endif + + +// Force on redirecting all allocations to the process heap on Win64, +// which currently means the GC. This is to make AppVerifier more effective +// at catching memory stomps. +#if defined( _WIN64 ) + + #define FORCE_PROCESS_HEAP + +#elif defined( _WIN32 ) +// Define this to force using the OS Heap* functions for allocations. This is useful +// in conjunction with AppVerifier/PageHeap in order to find memory problems, and +// also allows ETW/xperf tracing to be used to record allocations. +// Normally the command-line option -processheap can be used instead. +//#define FORCE_PROCESS_HEAP + +#define ALLOW_PROCESS_HEAP +#endif + +// Track this to decide how to handle out-of-memory. +static bool s_bPageHeapEnabled = false; + +#ifdef TIME_ALLOC +CAverageCycleCounter g_MallocCounter; +CAverageCycleCounter g_ReallocCounter; +CAverageCycleCounter g_FreeCounter; + +#define PrintOne( name ) \ + Msg("%-48s: %6.4f avg (%8.1f total, %7.3f peak, %5d iters)\n", \ + #name, \ + g_##name##Counter.GetAverageMilliseconds(), \ + g_##name##Counter.GetTotalMilliseconds(), \ + g_##name##Counter.GetPeakMilliseconds(), \ + g_##name##Counter.GetIters() ); \ + memset( &g_##name##Counter, 0, sizeof(g_##name##Counter) ) + +void PrintAllocTimes() +{ + PrintOne( Malloc ); + PrintOne( Realloc ); + PrintOne( Free ); +} + +#define PROFILE_ALLOC(name) CAverageTimeMarker name##_ATM( &g_##name##Counter ) + +#else +#define PROFILE_ALLOC( name ) ((void)0) +#define PrintAllocTimes() ((void)0) +#endif + +#if _MSC_VER < 1400 && defined( MSVC ) && !defined(_STATIC_LINKED) && (defined(_DEBUG) || defined(USE_MEM_DEBUG)) +void *operator new( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return ::operator new( nSize ); +} + +void *operator new[] ( unsigned int nSize, int nBlockUse, const char *pFileName, int nLine ) +{ + return ::operator new[]( nSize ); +} +#endif + +#if (!defined(_DEBUG) && !defined(USE_MEM_DEBUG)) + +// Support for CHeapMemAlloc for easy switching to using the process heap. +#ifdef ALLOW_PROCESS_HEAP + +// Round a size up to a multiple of 4 KB to aid in calculating how much +// memory is required if full pageheap is enabled. +static size_t RoundUpToPage( size_t nSize ) +{ + nSize += 0xFFF; + nSize &= ~0xFFF; + return nSize; +} + +// Convenience function to deal with the necessary type-casting +static void InterlockedAddSizeT( size_t volatile *Addend, size_t Value ) +{ +#ifdef PLATFORM_WINDOWS_PC32 + COMPILE_TIME_ASSERT( sizeof( size_t ) == sizeof( int32 ) ); + InterlockedExchangeAdd( ( LONG* )Addend, LONG( Value ) ); +#else + InterlockedExchangeAdd64( ( LONGLONG* )Addend, LONGLONG( Value ) ); +#endif +} + +class CHeapMemAlloc : public IMemAlloc +{ +public: + CHeapMemAlloc() + { + // Make sure that we return 64-bit addresses in 64-bit builds. + ReserveBottomMemory(); + + // Do all allocations with the shared process heap so that we can still + // allocate from one DLL and free in another. + m_heap = GetProcessHeap(); + } + + void Init( bool bZeroMemory ) + { + m_HeapFlags = bZeroMemory ? HEAP_ZERO_MEMORY : 0; + + // Can't use Msg here because it isn't necessarily initialized yet. + if ( s_bPageHeapEnabled ) + { + OutputDebugStringA("PageHeap is on. Memory use will be larger than normal.\n" ); + } + else + { + OutputDebugStringA("PageHeap is off. Memory use will be normal.\n" ); + } + if( bZeroMemory ) + { + OutputDebugStringA( " HEAP_ZERO_MEMORY is specified.\n" ); + } + } + + // Release versions + virtual void *Alloc( size_t nSize ) + { + // Ensure that the constructor has run already. Poorly defined + // order of construction can result in the allocator being used + // before it is constructed. Which could be bad. + if ( !m_heap ) + __debugbreak(); + void* pMem = HeapAlloc( m_heap, m_HeapFlags, nSize ); + if ( pMem ) + { + InterlockedAddSizeT( &m_nOutstandingBytes, nSize ); + InterlockedAddSizeT( &m_nOutstandingPageHeapBytes, RoundUpToPage( nSize ) ); + InterlockedIncrement( &m_nOutstandingAllocations ); + InterlockedIncrement( &m_nLifetimeAllocations ); + } + else if ( nSize ) + { + // Having PageHeap enabled leads to lots of allocation failures. These + // then lead to crashes. In order to avoid confusion about the cause of + // these crashes, halt immediately on allocation failures. + __debugbreak(); + InterlockedIncrement( &m_nAllocFailures ); + } + + return pMem; + } + virtual void *Realloc( void *pMem, size_t nSize ) + { + // If you pass zero to HeapReAlloc then it fails (with GetLastError() saying S_OK!) + // so only call HeapReAlloc if pMem is non-zero. + if ( pMem ) + { + if ( !nSize ) + { + // Call the regular free function. + Free( pMem ); + return 0; + } + size_t nOldSize = HeapSize( m_heap, 0, pMem ); + void* pNewMem = HeapReAlloc( m_heap, m_HeapFlags, pMem, nSize ); + + // If we successfully allocated the requested memory (zero counts as + // success if we requested zero bytes) then update the counters for the + // change. + if ( pNewMem ) + { + InterlockedAddSizeT( &m_nOutstandingBytes, nSize - nOldSize ); + InterlockedAddSizeT( &m_nOutstandingPageHeapBytes, RoundUpToPage( nSize ) ); + InterlockedAddSizeT( &m_nOutstandingPageHeapBytes, 0 - RoundUpToPage( nOldSize ) ); + // Outstanding allocation count isn't affected by Realloc, but + // lifetime allocation count is. + InterlockedIncrement( &m_nLifetimeAllocations ); + } + else + { + // Having PageHeap enabled leads to lots of allocation failures. These + // then lead to crashes. In order to avoid confusion about the cause of + // these crashes, halt immediately on allocation failures. + __debugbreak(); + InterlockedIncrement( &m_nAllocFailures ); + } + return pNewMem; + } + + // Call the regular alloc function. + return Alloc( nSize ); + } + virtual void Free( void *pMem ) + { + if ( pMem ) + { + size_t nOldSize = HeapSize( m_heap, 0, pMem ); + InterlockedAddSizeT( &m_nOutstandingBytes, 0 - nOldSize ); + InterlockedAddSizeT( &m_nOutstandingPageHeapBytes, 0 - RoundUpToPage( nOldSize ) ); + InterlockedDecrement( &m_nOutstandingAllocations ); + HeapFree( m_heap, 0, pMem ); + } + } + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ) { return 0; } + + // Debug versions + virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ) { return Alloc( nSize ); } + virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) { return Realloc(pMem, nSize); } + virtual void Free( void *pMem, const char *pFileName, int nLine ) { Free( pMem ); } + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) { return 0; } + +#ifdef MEMALLOC_SUPPORTS_ALIGNED_ALLOCATIONS + // Not currently implemented + #error +#endif + + virtual void *RegionAlloc( int region, size_t nSize ) { __debugbreak(); return 0; } + virtual void *RegionAlloc( int region, size_t nSize, const char *pFileName, int nLine ) { __debugbreak(); return 0; } + + // Returns size of a particular allocation + // If zero is returned then return the total size of allocated memory. + virtual size_t GetSize( void *pMem ) + { + if ( !pMem ) + { + return m_nOutstandingBytes; + } + return HeapSize( m_heap, 0, pMem ); + } + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo( const char *pFileName, int nLine ) {} + virtual void PopAllocDbgInfo() {} + + virtual long CrtSetBreakAlloc( long lNewBreakAlloc ) { return 0; } + virtual int CrtSetReportMode( int nReportType, int nReportMode ) { return 0; } + virtual int CrtIsValidHeapPointer( const void *pMem ) { return 0; } + virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ) { return 0; } + virtual int CrtCheckMemory( void ) { return 0; } + virtual int CrtSetDbgFlag( int nNewFlag ) { return 0; } + virtual void CrtMemCheckpoint( _CrtMemState *pState ) {} + virtual void* CrtSetReportFile( int nRptType, void* hFile ) { return 0; } + virtual void* CrtSetReportHook( void* pfnNewHook ) { return 0; } + virtual int CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ) { return 0; } + virtual int heapchk() { return -2/*_HEAPOK*/; } + + virtual void DumpStats() + { + const size_t MB = 1024 * 1024; + Msg( "Sorry -- no stats saved to file memstats.txt when the heap allocator is enabled.\n" ); + // Print requested memory. + Msg( "%u MB allocated.\n", ( unsigned )( m_nOutstandingBytes / MB ) ); + // Print memory after rounding up to pages. + Msg( "%u MB assuming maximum PageHeap overhead.\n", ( unsigned )( m_nOutstandingPageHeapBytes / MB )); + // Print memory after adding in reserved page after every allocation. Do 64-bit calculations + // because the pageHeap required memory can easily go over 4 GB. + __int64 pageHeapBytes = m_nOutstandingPageHeapBytes + m_nOutstandingAllocations * 4096LL; + Msg( "%u MB address space used assuming maximum PageHeap overhead.\n", ( unsigned )( pageHeapBytes / MB )); + Msg( "%u outstanding allocations (%d delta).\n", ( unsigned )m_nOutstandingAllocations, ( int )( m_nOutstandingAllocations - m_nOldOutstandingAllocations ) ); + Msg( "%u lifetime allocations (%u delta).\n", ( unsigned )m_nLifetimeAllocations, ( unsigned )( m_nLifetimeAllocations - m_nOldLifetimeAllocations ) ); + Msg( "%u allocation failures.\n", ( unsigned )m_nAllocFailures ); + + // Update the numbers on outstanding and lifetime allocation counts so + // that we can print out deltas. + m_nOldOutstandingAllocations = m_nOutstandingAllocations; + m_nOldLifetimeAllocations = m_nLifetimeAllocations; + } + virtual void DumpStatsFileBase( char const *pchFileBase ) {} + virtual size_t ComputeMemoryUsedBy( char const *pchSubStr ) { return 0; } + virtual void GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ) {} + + virtual bool IsDebugHeap() { return false; } + + virtual uint32 GetDebugInfoSize() { return 0; } + virtual void SaveDebugInfo( void *pvDebugInfo ) { } + virtual void RestoreDebugInfo( const void *pvDebugInfo ) {} + virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) {} + + virtual void GetActualDbgInfo( const char *&pFileName, int &nLine ) {} + virtual void RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) {} + virtual void RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) {} + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void OutOfMemory( size_t nBytesAttempted = 0 ) {} + + virtual void CompactHeap() {} + virtual void CompactIncremental() {} + + virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) { return 0; } + + void DumpBlockStats( void *p ) {} + +#if defined( _MEMTEST ) + // Not currently implemented + #error +#endif + + virtual size_t MemoryAllocFailed() { return 0; } + +private: + // Handle to the process heap. + HANDLE m_heap; + uint32 m_HeapFlags; + + // Total outstanding bytes allocated. + volatile size_t m_nOutstandingBytes; + + // Total outstanding committed bytes assuming that all allocations are + // put on individual 4-KB pages (true when using full PageHeap from + // App Verifier). + volatile size_t m_nOutstandingPageHeapBytes; + + // Total outstanding allocations. With PageHeap enabled each allocation + // requires an extra 4-KB page of address space. + volatile LONG m_nOutstandingAllocations; + LONG m_nOldOutstandingAllocations; + + // Total allocations without subtracting freed memory. + volatile LONG m_nLifetimeAllocations; + LONG m_nOldLifetimeAllocations; + + // Total number of allocation failures. + volatile LONG m_nAllocFailures; +}; + +#endif //ALLOW_PROCESS_HEAP + +//----------------------------------------------------------------------------- +// Singletons... +//----------------------------------------------------------------------------- +#pragma warning( disable:4074 ) // warning C4074: initializers put in compiler reserved initialization area +#pragma init_seg( compiler ) + +static CStdMemAlloc s_StdMemAlloc CONSTRUCT_EARLY; + +#ifndef TIER0_VALIDATE_HEAP +IMemAlloc *g_pMemAlloc = &s_StdMemAlloc; +#else +IMemAlloc *g_pActualAlloc = &s_StdMemAlloc; +#endif + +#if defined(ALLOW_PROCESS_HEAP) && !defined(TIER0_VALIDATE_HEAP) +void EnableHeapMemAlloc( bool bZeroMemory ) +{ + // Place this here to guarantee it is constructed + // before we call Init. + static CHeapMemAlloc s_HeapMemAlloc; + static bool s_initCalled = false; + + if ( !s_initCalled ) + { + s_HeapMemAlloc.Init( bZeroMemory ); + g_pMemAlloc = &s_HeapMemAlloc; + s_initCalled = true; + } +} + +// Check whether PageHeap (part of App Verifier) has been enabled for this process. +// It specifically checks whether it was enabled by the EnableAppVerifier.bat +// batch file. This can be used to automatically enable -processheap when +// App Verifier is in use. +static bool IsPageHeapEnabled( bool& bETWHeapEnabled ) +{ + // Assume false. + bool result = false; + bETWHeapEnabled = false; + + // First we get the application's name so we can look in the registry + // for App Verifier settings. + HMODULE exeHandle = GetModuleHandle( 0 ); + if ( exeHandle ) + { + char appName[ MAX_PATH ]; + if ( GetModuleFileNameA( exeHandle, appName, ARRAYSIZE( appName ) ) ) + { + // Guarantee null-termination -- not guaranteed on Windows XP! + appName[ ARRAYSIZE( appName ) - 1 ] = 0; + // Find the file part of the name. + const char* pFilePart = strrchr( appName, '\\' ); + if ( pFilePart ) + { + ++pFilePart; + size_t len = strlen( pFilePart ); + if ( len > 0 && pFilePart[ len - 1 ] == ' ' ) + { + OutputDebugStringA( "Trailing space on executable name! This will cause Application Verifier and ETW Heap tracing to fail!\n" ); + DebuggerBreakIfDebugging(); + } + + // Generate the key name for App Verifier settings for this process. + char regPathName[ MAX_PATH ]; + _snprintf( regPathName, ARRAYSIZE( regPathName ), + "Software\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\%s", + pFilePart ); + regPathName[ ARRAYSIZE( regPathName ) - 1 ] = 0; + + HKEY key; + LONG regResult = RegOpenKeyA( HKEY_LOCAL_MACHINE, + regPathName, + &key ); + if ( regResult == ERROR_SUCCESS ) + { + // If PageHeapFlags exists then that means that App Verifier is enabled + // for this application. The StackTraceDatabaseSizeInMB is only + // set by Valve's enabling batch file so this indicates that + // a developer at Valve is using App Verifier. + if ( RegQueryValueExA( key, "StackTraceDatabaseSizeInMB", 0, NULL, NULL, NULL ) == ERROR_SUCCESS && + RegQueryValueExA( key, "PageHeapFlags", 0, NULL, NULL, NULL) == ERROR_SUCCESS ) + { + result = true; + } + + if ( RegQueryValueExA( key, "TracingFlags", 0, NULL, NULL, NULL) == ERROR_SUCCESS ) + bETWHeapEnabled = true; + + RegCloseKey( key ); + } + } + } + } + + return result; +} + +// Check for various allocator overrides such as -processheap and -reservelowmem. +// Returns true if -processheap is enabled, by a command line switch or other method. +bool CheckWindowsAllocSettings( const char* upperCommandLine ) +{ + // Are we doing ETW heap profiling? + bool bETWHeapEnabled = false; + s_bPageHeapEnabled = IsPageHeapEnabled( bETWHeapEnabled ); + + // Should we reserve the bottom 4 GB of RAM in order to flush out pointer + // truncation bugs? This helps ensure 64-bit compatibility. + // However this needs to be off by default to avoid causing compatibility problems, + // with Steam detours and other systems. It should also be disabled when PageHeap + // is on because for some reason the combination turns into 4 GB of working set, which + // can easily cause problems. + if ( strstr( upperCommandLine, "-RESERVELOWMEM" ) && !s_bPageHeapEnabled ) + ReserveBottomMemory(); + + // Uninitialized data, including pointers, is often set to 0xFFEEFFEE. + // If we reserve that block of memory then we can turn these pointer + // dereferences into crashes a little bit earlier and more reliably. + // We don't really care whether this allocation succeeds, but it's + // worth trying. Note that we do this in all cases -- whether we are using + // -processheap or not. + VirtualAlloc( (void*)0xFFEEFFEE, 1, MEM_RESERVE, PAGE_NOACCESS ); + + // Enable application termination (breakpoint) on heap corruption. This is + // better than trying to patch it up and continue, both from a security and + // a bug-finding point of view. Do this always on Windows since the heap is + // used by video drivers and other in-proc components. + //HeapSetInformation( NULL, HeapEnableTerminationOnCorruption, NULL, 0 ); + // The HeapEnableTerminationOnCorruption requires a recent platform SDK, + // so fake it up. +#if defined(PLATFORM_WINDOWS_PC) + HeapSetInformation( NULL, (HEAP_INFORMATION_CLASS)1, NULL, 0 ); +#endif + + bool bZeroMemory = false; + bool bProcessHeap = false; + // Should we force using the process heap? This is handy for gathering memory + // statistics with ETW/xperf. When using App Verifier -processheap is automatically + // turned on. + if ( strstr( upperCommandLine, "-PROCESSHEAP" ) ) + { + bProcessHeap = true; + bZeroMemory = !!strstr( upperCommandLine, "-PROCESSHEAPZEROMEM" ); + } + + // Unless specifically disabled, turn on -processheap if pageheap or ETWHeap tracing + // are enabled. + if ( !strstr( upperCommandLine, "-NOPROCESSHEAP" ) && ( s_bPageHeapEnabled || bETWHeapEnabled ) ) + bProcessHeap = true; + + if ( bProcessHeap ) + { + // Now all allocations will go through the system heap. + EnableHeapMemAlloc( bZeroMemory ); + } + + return bProcessHeap; +} + +class CInitGlobalMemAllocPtr +{ +public: + CInitGlobalMemAllocPtr() + { + char *pStr = (char*)Plat_GetCommandLineA(); + if ( pStr ) + { + char tempStr[512]; + strncpy( tempStr, pStr, sizeof( tempStr ) - 1 ); + tempStr[ sizeof( tempStr ) - 1 ] = 0; + _strupr( tempStr ); + + CheckWindowsAllocSettings( tempStr ); + } +#if defined(FORCE_PROCESS_HEAP) + // This may cause EnableHeapMemAlloc to be called twice, but that's okay. + EnableHeapMemAlloc( false ); +#endif + } +}; +CInitGlobalMemAllocPtr sg_InitGlobalMemAllocPtr; +#endif + +#ifdef _WIN32 +//----------------------------------------------------------------------------- +// Small block heap (multi-pool) +//----------------------------------------------------------------------------- + +#ifndef NO_SBH +#ifdef ALLOW_NOSBH +static bool g_UsingSBH = true; +#define UsingSBH() g_UsingSBH +#else +#define UsingSBH() true +#endif +#else +#define UsingSBH() false +#endif + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +template +inline T MemAlign( T val, size_t alignment ) +{ + return (T)( ( (size_t)val + alignment - 1 ) & ~( alignment - 1 ) ); +} +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +void CSmallBlockPool::Init( unsigned nBlockSize, byte *pBase, unsigned initialCommit ) +{ + if ( !( nBlockSize % MIN_SBH_ALIGN == 0 && nBlockSize >= MIN_SBH_BLOCK && nBlockSize >= sizeof(TSLNodeBase_t) ) ) + DebuggerBreak(); + + m_nBlockSize = nBlockSize; + m_pCommitLimit = m_pNextAlloc = m_pBase = pBase; + m_pAllocLimit = m_pBase + MAX_POOL_REGION; + + if ( initialCommit ) + { + initialCommit = MemAlign( initialCommit, SBH_PAGE_SIZE ); + if ( !VirtualAlloc( m_pCommitLimit, initialCommit, VA_COMMIT_FLAGS, PAGE_READWRITE ) ) + { + Assert( 0 ); + return; + } + m_pCommitLimit += initialCommit; + } +} + +size_t CSmallBlockPool::GetBlockSize() +{ + return m_nBlockSize; +} + +bool CSmallBlockPool::IsOwner( void *p ) +{ + return ( p >= m_pBase && p < m_pAllocLimit ); + } + +void *CSmallBlockPool::Alloc() + { + void *pResult = m_FreeList.Pop(); + if ( !pResult ) + { + int nBlockSize = m_nBlockSize; + byte *pCommitLimit; + byte *pNextAlloc; + for (;;) + { + pCommitLimit = m_pCommitLimit; + pNextAlloc = m_pNextAlloc; + if ( pNextAlloc + nBlockSize <= pCommitLimit ) + { + if ( m_pNextAlloc.AssignIf( pNextAlloc, pNextAlloc + m_nBlockSize ) ) + { + pResult = pNextAlloc; + break; + } + } + else + { + AUTO_LOCK( m_CommitMutex ); + if ( pCommitLimit == m_pCommitLimit ) + { + if ( pCommitLimit + COMMIT_SIZE <= m_pAllocLimit ) + { + if ( !VirtualAlloc( pCommitLimit, COMMIT_SIZE, VA_COMMIT_FLAGS, PAGE_READWRITE ) ) + { + Assert( 0 ); + return NULL; + } + + m_pCommitLimit = pCommitLimit + COMMIT_SIZE; + } + else + { + return NULL; + } + } + } + } + } + return pResult; +} + +void CSmallBlockPool::Free( void *p ) + { + Assert( IsOwner( p ) ); + + m_FreeList.Push( p ); +} + +// Count the free blocks. +int CSmallBlockPool::CountFreeBlocks() +{ + return m_FreeList.Count(); +} + +// Size of committed memory managed by this heap: +int CSmallBlockPool::GetCommittedSize() +{ + unsigned totalSize = (unsigned)m_pCommitLimit - (unsigned)m_pBase; + Assert( 0 != m_nBlockSize ); + + return totalSize; +} + +// Return the total blocks memory is committed for in the heap +int CSmallBlockPool::CountCommittedBlocks() +{ + return GetCommittedSize() / GetBlockSize(); +} + +// Count the number of allocated blocks in the heap: +int CSmallBlockPool::CountAllocatedBlocks() +{ + return CountCommittedBlocks( ) - ( CountFreeBlocks( ) + ( m_pCommitLimit - (byte *)m_pNextAlloc ) / GetBlockSize() ); +} + +int CSmallBlockPool::Compact() +{ + int nBytesFreed = 0; + if ( m_FreeList.Count() ) +{ + int i; + int nFree = CountFreeBlocks(); + FreeBlock_t **pSortArray = (FreeBlock_t **)malloc( nFree * sizeof(FreeBlock_t *) ); // can't use new because will reenter + + if ( !pSortArray ) + { + return 0; + } + + i = 0; + while ( i < nFree ) + { + pSortArray[i++] = m_FreeList.Pop(); + } + + std::sort( pSortArray, pSortArray + nFree ); + + byte *pOldNextAlloc = m_pNextAlloc; + + for ( i = nFree - 1; i >= 0; i-- ) + { + if ( (byte *)pSortArray[i] == m_pNextAlloc - m_nBlockSize ) + { + pSortArray[i] = NULL; + m_pNextAlloc -= m_nBlockSize; + } + else + { + break; + } + } + + if ( pOldNextAlloc != m_pNextAlloc ) + { + byte *pNewCommitLimit = MemAlign( (byte *)m_pNextAlloc, SBH_PAGE_SIZE ); + if ( pNewCommitLimit < m_pCommitLimit ) + { + nBytesFreed = m_pCommitLimit - pNewCommitLimit; + VirtualFree( pNewCommitLimit, nBytesFreed, MEM_DECOMMIT ); + m_pCommitLimit = pNewCommitLimit; + } + } + + if ( pSortArray[0] ) + { + for ( i = 0; i < nFree ; i++ ) + { + if ( !pSortArray[i] ) + { + break; + } + m_FreeList.Push( pSortArray[i] ); + } + } + + free( pSortArray ); + } + + return nBytesFreed; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +#define GetInitialCommitForPool( i ) 0 + +CSmallBlockHeap::CSmallBlockHeap() +{ + // Make sure that we return 64-bit addresses in 64-bit builds. + ReserveBottomMemory(); + + if ( !UsingSBH() ) + { + return; + } + + m_pBase = (byte *)VirtualAlloc( NULL, NUM_POOLS * MAX_POOL_REGION, VA_RESERVE_FLAGS, PAGE_NOACCESS ); + m_pLimit = m_pBase + NUM_POOLS * MAX_POOL_REGION; + + // Build a lookup table used to find the correct pool based on size + const int MAX_TABLE = MAX_SBH_BLOCK >> 2; + int i = 0; + int nBytesElement = 0; + byte *pCurBase = m_pBase; + CSmallBlockPool *pCurPool = NULL; + int iCurPool = 0; + +#if _M_X64 + // Blocks sized 0 - 256 are in pools in increments of 16 + for ( ; i < 64 && i < MAX_TABLE; i++ ) + { + if ( (i + 1) % 4 == 1) + { + nBytesElement += 16; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement, pCurBase, GetInitialCommitForPool(iCurPool) ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + pCurBase += MAX_POOL_REGION; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } +#else + // Blocks sized 0 - 128 are in pools in increments of 8 + for ( ; i < 32; i++ ) + { + if ( (i + 1) % 2 == 1) + { + nBytesElement += 8; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement, pCurBase, GetInitialCommitForPool(iCurPool) ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + pCurBase += MAX_POOL_REGION; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 129 - 256 are in pools in increments of 16 + for ( ; i < 64; i++ ) + { + if ( (i + 1) % 4 == 1) + { + nBytesElement += 16; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement, pCurBase, GetInitialCommitForPool(iCurPool) ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + pCurBase += MAX_POOL_REGION; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } +#endif + + // Blocks sized 257 - 512 are in pools in increments of 32 + for ( ; i < 128; i++ ) + { + if ( (i + 1) % 8 == 1) + { + nBytesElement += 32; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement, pCurBase, GetInitialCommitForPool(iCurPool) ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + pCurBase += MAX_POOL_REGION; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 513 - 768 are in pools in increments of 64 + for ( ; i < 192; i++ ) + { + if ( (i + 1) % 16 == 1) + { + nBytesElement += 64; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement, pCurBase, GetInitialCommitForPool(iCurPool) ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + pCurBase += MAX_POOL_REGION; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 769 - 1024 are in pools in increments of 128 + for ( ; i < 256; i++ ) + { + if ( (i + 1) % 32 == 1) + { + nBytesElement += 128; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement, pCurBase, GetInitialCommitForPool(iCurPool) ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + pCurBase += MAX_POOL_REGION; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 1025 - 2048 are in pools in increments of 256 + for ( ; i < MAX_TABLE; i++ ) + { + if ( (i + 1) % 64 == 1) + { + nBytesElement += 256; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement, pCurBase, GetInitialCommitForPool(iCurPool) ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + pCurBase += MAX_POOL_REGION; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + Assert( iCurPool == NUM_POOLS ); +} + +bool CSmallBlockHeap::ShouldUse( size_t nBytes ) +{ + return ( UsingSBH() && nBytes <= MAX_SBH_BLOCK ); +} + +bool CSmallBlockHeap::IsOwner( void * p ) +{ + return ( UsingSBH() && p >= m_pBase && p < m_pLimit ); +} + +void *CSmallBlockHeap::Alloc( size_t nBytes ) +{ + if ( nBytes == 0) + { + nBytes = 1; + } + Assert( ShouldUse( nBytes ) ); + CSmallBlockPool *pPool = FindPool( nBytes ); + + void *p = pPool->Alloc(); + if ( p ) + { + return p; + } + + if ( s_StdMemAlloc.CallAllocFailHandler( nBytes ) >= nBytes ) + { + p = pPool->Alloc(); + if ( p ) + { + return p; +} + } + + void *pRet = malloc( nBytes ); + if ( !pRet ) + { + s_StdMemAlloc.SetCRTAllocFailed( nBytes ); + } + return pRet; +} + +void *CSmallBlockHeap::Realloc( void *p, size_t nBytes ) +{ + if ( nBytes == 0) + { + nBytes = 1; + } + + CSmallBlockPool *pOldPool = FindPool( p ); + CSmallBlockPool *pNewPool = ( ShouldUse( nBytes ) ) ? FindPool( nBytes ) : NULL; + + if ( pOldPool == pNewPool ) + { + return p; + } + + void *pNewBlock = NULL; + + if ( pNewPool ) + { + pNewBlock = pNewPool->Alloc(); + + if ( !pNewBlock ) + { + if ( s_StdMemAlloc.CallAllocFailHandler( nBytes ) >= nBytes ) + { + pNewBlock = pNewPool->Alloc(); + } + } + } + + if ( !pNewBlock ) + { + pNewBlock = malloc( nBytes ); + if ( !pNewBlock ) + { + s_StdMemAlloc.SetCRTAllocFailed( nBytes ); + } + } + + if ( pNewBlock ) + { + int nBytesCopy = min( nBytes, pOldPool->GetBlockSize() ); + memcpy( pNewBlock, p, nBytesCopy ); + } + + pOldPool->Free( p ); + + return pNewBlock; +} + +void CSmallBlockHeap::Free( void *p ) + { + CSmallBlockPool *pPool = FindPool( p ); + pPool->Free( p ); + } + +size_t CSmallBlockHeap::GetSize( void *p ) +{ + CSmallBlockPool *pPool = FindPool( p ); + return pPool->GetBlockSize(); +} + +void CSmallBlockHeap::DumpStats( FILE *pFile ) +{ + bool bSpew = true; + + if ( pFile ) + { + for ( int i = 0; i < NUM_POOLS; i++ ) + { + // output for vxconsole parsing + fprintf( pFile, "Pool %i: Size: %llu Allocated: %i Free: %i Committed: %i CommittedSize: %i\n", + i, + (uint64)m_Pools[i].GetBlockSize(), + m_Pools[i].CountAllocatedBlocks(), + m_Pools[i].CountFreeBlocks(), + m_Pools[i].CountCommittedBlocks(), + m_Pools[i].GetCommittedSize() ); + } + bSpew = false; + } + + if ( bSpew ) + { + unsigned bytesCommitted = 0; + unsigned bytesAllocated = 0; + + for ( int i = 0; i < NUM_POOLS; i++ ) + { + Msg( "Pool %i: (size: %llu) blocks: allocated:%i free:%i committed:%i (committed size:%u kb)\n",i, (uint64)m_Pools[i].GetBlockSize(),m_Pools[i].CountAllocatedBlocks(), m_Pools[i].CountFreeBlocks(),m_Pools[i].CountCommittedBlocks(), m_Pools[i].GetCommittedSize() / 1024); + + bytesCommitted += m_Pools[i].GetCommittedSize(); + bytesAllocated += ( m_Pools[i].CountAllocatedBlocks() * m_Pools[i].GetBlockSize() ); + } + + Msg( "Totals: Committed:%u kb Allocated:%u kb\n", bytesCommitted / 1024, bytesAllocated / 1024 ); + } +} + +int CSmallBlockHeap::Compact() +{ + int nBytesFreed = 0; + for( int i = 0; i < NUM_POOLS; i++ ) + { + nBytesFreed += m_Pools[i].Compact(); + } + return nBytesFreed; +} + +CSmallBlockPool *CSmallBlockHeap::FindPool( size_t nBytes ) +{ + return m_PoolLookup[(nBytes - 1) >> 2]; +} + +CSmallBlockPool *CSmallBlockHeap::FindPool( void *p ) +{ + size_t i = ((byte *)p - m_pBase) / MAX_POOL_REGION; + return &m_Pools[i]; +} + + +#endif + +#if USE_PHYSICAL_SMALL_BLOCK_HEAP + +CX360SmallBlockPool *CX360SmallBlockPool::gm_AddressToPool[BYTES_X360_SBH/PAGESIZE_X360_SBH]; +byte *CX360SmallBlockPool::gm_pPhysicalBlock; +byte *CX360SmallBlockPool::gm_pPhysicalBase; +byte *CX360SmallBlockPool::gm_pPhysicalLimit; + +void CX360SmallBlockPool::Init( unsigned nBlockSize ) +{ + if ( !gm_pPhysicalBlock ) + { + gm_pPhysicalBase = (byte *)XPhysicalAlloc( BYTES_X360_SBH, MAXULONG_PTR, 4096, PAGE_READWRITE | MEM_16MB_PAGES ); + gm_pPhysicalLimit = gm_pPhysicalBase + BYTES_X360_SBH; + gm_pPhysicalBlock = gm_pPhysicalBase; + } + + if ( !( nBlockSize % MIN_SBH_ALIGN == 0 && nBlockSize >= MIN_SBH_BLOCK && nBlockSize >= sizeof(TSLNodeBase_t) ) ) + DebuggerBreak(); + + m_nBlockSize = nBlockSize; + m_pCurBlockEnd = m_pNextAlloc = NULL; + m_CommittedSize = 0; +} + +size_t CX360SmallBlockPool::GetBlockSize() + { + return m_nBlockSize; +} + +bool CX360SmallBlockPool::IsOwner( void *p ) + { + return ( FindPool( p ) == this ); + } + +void *CX360SmallBlockPool::Alloc() +{ + void *pResult = m_FreeList.Pop(); + if ( !pResult ) + { + if ( !m_pNextAlloc && gm_pPhysicalBlock >= gm_pPhysicalLimit ) + { + return NULL; + } + + int nBlockSize = m_nBlockSize; + byte *pCurBlockEnd; + byte *pNextAlloc; + for (;;) + { + pCurBlockEnd = m_pCurBlockEnd; + pNextAlloc = m_pNextAlloc; + if ( pNextAlloc + nBlockSize <= pCurBlockEnd ) + { + if ( m_pNextAlloc.AssignIf( pNextAlloc, pNextAlloc + m_nBlockSize ) ) + { + pResult = pNextAlloc; + break; + } + } + else + { + AUTO_LOCK( m_CommitMutex ); + + if ( pCurBlockEnd == m_pCurBlockEnd ) + { + for (;;) + { + if ( gm_pPhysicalBlock >= gm_pPhysicalLimit ) + { + m_pCurBlockEnd = m_pNextAlloc = NULL; + return NULL; + } + byte *pPhysicalBlock = gm_pPhysicalBlock; + if ( ThreadInterlockedAssignPointerIf( (void **)&gm_pPhysicalBlock, (void *)(pPhysicalBlock + PAGESIZE_X360_SBH), (void *)pPhysicalBlock ) ) + { + int index = (size_t)((byte *)pPhysicalBlock - gm_pPhysicalBase) / PAGESIZE_X360_SBH; + gm_AddressToPool[index] = this; + m_pNextAlloc = pPhysicalBlock; + m_CommittedSize += PAGESIZE_X360_SBH; + __sync(); + m_pCurBlockEnd = pPhysicalBlock + PAGESIZE_X360_SBH; + break; + } + } + } + } +} + } + return pResult; +} + +void CX360SmallBlockPool::Free( void *p ) +{ + Assert( IsOwner( p ) ); + + m_FreeList.Push( p ); +} + +// Count the free blocks. +int CX360SmallBlockPool::CountFreeBlocks() +{ + return m_FreeList.Count(); +} + +// Size of committed memory managed by this heap: +int CX360SmallBlockPool::GetCommittedSize() +{ + return m_CommittedSize; +} + +// Return the total blocks memory is committed for in the heap +int CX360SmallBlockPool::CountCommittedBlocks() +{ + return GetCommittedSize() / GetBlockSize(); +} + +// Count the number of allocated blocks in the heap: +int CX360SmallBlockPool::CountAllocatedBlocks() +{ + int nBytesPossible = ( m_pNextAlloc ) ? ( m_pCurBlockEnd - (byte *)m_pNextAlloc ) : 0; + return CountCommittedBlocks( ) - ( CountFreeBlocks( ) + nBytesPossible / GetBlockSize() ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +#define GetInitialCommitForPool( i ) 0 + +CX360SmallBlockHeap::CX360SmallBlockHeap() +{ + if ( !UsingSBH() ) +{ + return; + } + + // Build a lookup table used to find the correct pool based on size + const int MAX_TABLE = MAX_SBH_BLOCK >> 2; + int i = 0; + int nBytesElement = 0; + CX360SmallBlockPool *pCurPool = NULL; + int iCurPool = 0; + + // Blocks sized 0 - 128 are in pools in increments of 8 + for ( ; i < 32; i++ ) +{ + if ( (i + 1) % 2 == 1) + { + nBytesElement += 8; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } +} + + // Blocks sized 129 - 256 are in pools in increments of 16 + for ( ; i < 64; i++ ) +{ + if ( (i + 1) % 4 == 1) + { + nBytesElement += 16; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } +} + + + // Blocks sized 257 - 512 are in pools in increments of 32 + for ( ; i < 128; i++ ) +{ + if ( (i + 1) % 8 == 1) + { + nBytesElement += 32; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 513 - 768 are in pools in increments of 64 + for ( ; i < 192; i++ ) + { + if ( (i + 1) % 16 == 1) + { + nBytesElement += 64; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } +} + + // Blocks sized 769 - 1024 are in pools in increments of 128 + for ( ; i < 256; i++ ) +{ + if ( (i + 1) % 32 == 1) + { + nBytesElement += 128; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + // Blocks sized 1025 - 2048 are in pools in increments of 256 + for ( ; i < MAX_TABLE; i++ ) + { + if ( (i + 1) % 64 == 1) + { + nBytesElement += 256; + pCurPool = &m_Pools[iCurPool]; + pCurPool->Init( nBytesElement ); + iCurPool++; + m_PoolLookup[i] = pCurPool; + } + else + { + m_PoolLookup[i] = pCurPool; + } + } + + Assert( iCurPool == NUM_POOLS ); + } + +bool CX360SmallBlockHeap::ShouldUse( size_t nBytes ) +{ + return ( UsingSBH() && nBytes <= MAX_SBH_BLOCK ); +} + +bool CX360SmallBlockHeap::IsOwner( void * p ) +{ + int index = (size_t)((byte *)p - CX360SmallBlockPool::gm_pPhysicalBase) / PAGESIZE_X360_SBH; + return ( UsingSBH() && ( index >= 0 && index < ARRAYSIZE(CX360SmallBlockPool::gm_AddressToPool) ) ); + } + +void *CX360SmallBlockHeap::Alloc( size_t nBytes ) +{ + if ( nBytes == 0) + { + nBytes = 1; + } + Assert( ShouldUse( nBytes ) ); + CX360SmallBlockPool *pPool = FindPool( nBytes ); + + void *p = pPool->Alloc(); + if ( p ) + { + return p; + } + + return GetStandardSBH()->Alloc( nBytes ); +} + +void *CX360SmallBlockHeap::Realloc( void *p, size_t nBytes ) +{ + if ( nBytes == 0) + { + nBytes = 1; + } + + CX360SmallBlockPool *pOldPool = FindPool( p ); + CX360SmallBlockPool *pNewPool = ( ShouldUse( nBytes ) ) ? FindPool( nBytes ) : NULL; + + if ( pOldPool == pNewPool ) + { + return p; + } + + void *pNewBlock = NULL; + + if ( pNewPool ) + { + pNewBlock = pNewPool->Alloc(); + + if ( !pNewBlock ) + { + pNewBlock = GetStandardSBH()->Alloc( nBytes ); + } + } + + if ( !pNewBlock ) + { + pNewBlock = malloc( nBytes ); + } + + if ( pNewBlock ) + { + int nBytesCopy = min( nBytes, pOldPool->GetBlockSize() ); + memcpy( pNewBlock, p, nBytesCopy ); + } + + pOldPool->Free( p ); + + return pNewBlock; +} + +void CX360SmallBlockHeap::Free( void *p ) +{ + CX360SmallBlockPool *pPool = FindPool( p ); + pPool->Free( p ); + } + +size_t CX360SmallBlockHeap::GetSize( void *p ) +{ + CX360SmallBlockPool *pPool = FindPool( p ); + return pPool->GetBlockSize(); +} + +void CX360SmallBlockHeap::DumpStats( FILE *pFile ) +{ + bool bSpew = true; + + if ( pFile ) + { + for( int i = 0; i < NUM_POOLS; i++ ) + { + // output for vxconsole parsing + fprintf( pFile, "Pool %i: Size: %u Allocated: %i Free: %i Committed: %i CommittedSize: %i\n", + i, + m_Pools[i].GetBlockSize(), + m_Pools[i].CountAllocatedBlocks(), + m_Pools[i].CountFreeBlocks(), + m_Pools[i].CountCommittedBlocks(), + m_Pools[i].GetCommittedSize() ); + } + bSpew = false; +} + + if ( bSpew ) +{ + unsigned bytesCommitted = 0; + unsigned bytesAllocated = 0; + + for( int i = 0; i < NUM_POOLS; i++ ) + { + + bytesCommitted += m_Pools[i].GetCommittedSize(); + bytesAllocated += ( m_Pools[i].CountAllocatedBlocks() * m_Pools[i].GetBlockSize() ); + } + + Msg( "Totals: Committed:%u kb Allocated:%u kb\n", bytesCommitted / 1024, bytesAllocated / 1024 ); + } +} + +CSmallBlockHeap *CX360SmallBlockHeap::GetStandardSBH() +{ + return &(GET_OUTER( CStdMemAlloc, m_LargePageSmallBlockHeap )->m_SmallBlockHeap); +} + +CX360SmallBlockPool *CX360SmallBlockHeap::FindPool( size_t nBytes ) + { + return m_PoolLookup[(nBytes - 1) >> 2]; + } + +CX360SmallBlockPool *CX360SmallBlockHeap::FindPool( void *p ) + { + return CX360SmallBlockPool::FindPool( p ); + } + + +#endif + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- + +void *CStdMemAlloc::Alloc( size_t nSize ) +{ + PROFILE_ALLOC(Malloc); + + void *pMem; + +#ifdef _WIN32 +#ifdef USE_PHYSICAL_SMALL_BLOCK_HEAP + if ( m_LargePageSmallBlockHeap.ShouldUse( nSize ) ) + { + pMem = m_LargePageSmallBlockHeap.Alloc( nSize ); + ApplyMemoryInitializations( pMem, nSize ); + return pMem; + } +#endif + + if ( m_SmallBlockHeap.ShouldUse( nSize ) ) + { + pMem = m_SmallBlockHeap.Alloc( nSize ); + ApplyMemoryInitializations( pMem, nSize ); + return pMem; +} + +#endif + + pMem = malloc( nSize ); + ApplyMemoryInitializations( pMem, nSize ); + if ( !pMem ) + { + SetCRTAllocFailed( nSize ); + } + return pMem; +} + +void *CStdMemAlloc::Realloc( void *pMem, size_t nSize ) +{ + if ( !pMem ) + { + return Alloc( nSize ); + } + + PROFILE_ALLOC(Realloc); + +#ifdef MEM_SBH_ENABLED +#ifdef USE_PHYSICAL_SMALL_BLOCK_HEAP + if ( m_LargePageSmallBlockHeap.IsOwner( pMem ) ) + { + return m_LargePageSmallBlockHeap.Realloc( pMem, nSize ); + } +#endif + + if ( m_SmallBlockHeap.IsOwner( pMem ) ) + { + return m_SmallBlockHeap.Realloc( pMem, nSize ); + } +#endif + + void *pRet = realloc( pMem, nSize ); + if ( !pRet ) + { + SetCRTAllocFailed( nSize ); + } + return pRet; +} + +void CStdMemAlloc::Free( void *pMem ) +{ + if ( !pMem ) + { + return; + } + + PROFILE_ALLOC(Free); + +#ifdef MEM_SBH_ENABLED +#ifdef USE_PHYSICAL_SMALL_BLOCK_HEAP + if ( m_LargePageSmallBlockHeap.IsOwner( pMem ) ) + { + m_LargePageSmallBlockHeap.Free( pMem ); + return; + } +#endif + + if ( m_SmallBlockHeap.IsOwner( pMem ) ) + { + m_SmallBlockHeap.Free( pMem ); + return; + } +#endif + + free( pMem ); +} + +void *CStdMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize ) + { + return NULL; + } + + +//----------------------------------------------------------------------------- +// Debug versions +//----------------------------------------------------------------------------- +void *CStdMemAlloc::Alloc( size_t nSize, const char *pFileName, int nLine ) +{ + return CStdMemAlloc::Alloc( nSize ); +} + +void *CStdMemAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + return CStdMemAlloc::Realloc( pMem, nSize ); + } + +void CStdMemAlloc::Free( void *pMem, const char *pFileName, int nLine ) +{ + CStdMemAlloc::Free( pMem ); +} + +void *CStdMemAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + return NULL; +} + +#if defined (LINUX) +#include +#elif defined (OSX) +#define malloc_usable_size( ptr ) malloc_size( ptr ) +extern "C" { + extern size_t malloc_size( const void *ptr ); +} +#endif + +//----------------------------------------------------------------------------- +// Returns size of a particular allocation +//----------------------------------------------------------------------------- +size_t CStdMemAlloc::GetSize( void *pMem ) +{ +#ifdef MEM_SBH_ENABLED + if ( !pMem ) + return CalcHeapUsed(); + else + { +#ifdef USE_PHYSICAL_SMALL_BLOCK_HEAP + if ( m_LargePageSmallBlockHeap.IsOwner( pMem ) ) + { + return m_LargePageSmallBlockHeap.GetSize( pMem ); + } +#endif + if ( m_SmallBlockHeap.IsOwner( pMem ) ) + { + return m_SmallBlockHeap.GetSize( pMem ); + } + return _msize( pMem ); + } +#else + return malloc_usable_size( pMem ); +#endif +} + + +//----------------------------------------------------------------------------- +// Force file + line information for an allocation +//----------------------------------------------------------------------------- +void CStdMemAlloc::PushAllocDbgInfo( const char *pFileName, int nLine ) +{ +} + +void CStdMemAlloc::PopAllocDbgInfo() +{ +} + +//----------------------------------------------------------------------------- +// FIXME: Remove when we make our own heap! Crt stuff we're currently using +//----------------------------------------------------------------------------- +long CStdMemAlloc::CrtSetBreakAlloc( long lNewBreakAlloc ) +{ + return 0; +} + +int CStdMemAlloc::CrtSetReportMode( int nReportType, int nReportMode ) +{ + return 0; +} + +int CStdMemAlloc::CrtIsValidHeapPointer( const void *pMem ) +{ + return 1; +} + +int CStdMemAlloc::CrtIsValidPointer( const void *pMem, unsigned int size, int access ) +{ + return 1; +} + +int CStdMemAlloc::CrtCheckMemory( void ) +{ + return 1; +} + +int CStdMemAlloc::CrtSetDbgFlag( int nNewFlag ) +{ + return 0; +} + +void CStdMemAlloc::CrtMemCheckpoint( _CrtMemState *pState ) +{ +} + +// FIXME: Remove when we have our own allocator +void* CStdMemAlloc::CrtSetReportFile( int nRptType, void* hFile ) +{ + return 0; +} + +void* CStdMemAlloc::CrtSetReportHook( void* pfnNewHook ) +{ + return 0; +} + +int CStdMemAlloc::CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ) +{ + return 0; +} + +int CStdMemAlloc::heapchk() +{ +#ifdef _WIN32 + return _HEAPOK; +#else + return 1; +#endif +} + +void CStdMemAlloc::DumpStats() +{ + DumpStatsFileBase( "memstats" ); +} + +void CStdMemAlloc::DumpStatsFileBase( char const *pchFileBase ) +{ +#ifdef _WIN32 + char filename[ 512 ]; + _snprintf( filename, sizeof( filename ) - 1, ( IsX360() ) ? "D:\\%s.txt" : "%s.txt", pchFileBase ); + filename[ sizeof( filename ) - 1 ] = 0; + FILE *pFile = fopen( filename, "wt" ); +#ifdef USE_PHYSICAL_SMALL_BLOCK_HEAP + fprintf( pFile, "X360 Large Page SBH:\n" ); + m_LargePageSmallBlockHeap.DumpStats(pFile); +#endif + fprintf( pFile, "\nSBH:\n" ); + m_SmallBlockHeap.DumpStats(pFile); // Dump statistics to small block heap + +#if defined( _X360 ) && !defined( _RETAIL ) + XBX_rMemDump( filename ); +#endif + + fclose( pFile ); +#endif +} + +void CStdMemAlloc::GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ) +{ + if ( !pUsedMemory || !pFreeMemory ) + return; + +#if defined ( _X360 ) + + // GlobalMemoryStatus tells us how much physical memory is free + MEMORYSTATUS stat; + ::GlobalMemoryStatus( &stat ); + *pFreeMemory = stat.dwAvailPhys; + + // NOTE: we do not count free memory inside our small block heaps, as this could be misleading + // (even with lots of SBH memory free, a single allocation over 2kb can still fail) + +#if defined( USE_DLMALLOC ) + // Account for free memory contained within DLMalloc + for ( int i = 0; i < ARRAYSIZE( g_AllocRegions ); i++ ) + { + mallinfo info = mspace_mallinfo( g_AllocRegions[ i ] ); + *pFreeMemory += info.fordblks; + } +#endif + + // Used is total minus free (discount the 32MB system reservation) + *pUsedMemory = ( stat.dwTotalPhys - 32*1024*1024 ) - *pFreeMemory; + +#else + + // no data + *pFreeMemory = 0; + *pUsedMemory = 0; + +#endif +} + +void CStdMemAlloc::CompactHeap() +{ +#if !defined( NO_SBH ) && defined( _WIN32 ) + int nBytesRecovered = m_SmallBlockHeap.Compact(); + Msg( "Compact freed %d bytes\n", nBytesRecovered ); +#endif +} + +MemAllocFailHandler_t CStdMemAlloc::SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) +{ + MemAllocFailHandler_t pfnPrevious = m_pfnFailHandler; + m_pfnFailHandler = pfnMemAllocFailHandler; + return pfnPrevious; +} + +size_t CStdMemAlloc::DefaultFailHandler( size_t nBytes ) +{ + if ( IsX360() && !IsRetail() ) + { +#ifdef _X360 + ExecuteOnce( + { + char buffer[256]; + _snprintf( buffer, sizeof( buffer ), "***** Memory pool overflow, attempted allocation size: %u ****\n", nBytes ); + XBX_OutputDebugString( buffer ); + } + ); +#endif + } + + return 0; +} + +#if defined( _MEMTEST ) +void CStdMemAlloc::void SetStatsExtraInfo( const char *pMapName, const char *pComment ) +{ +} +#endif + +void CStdMemAlloc::SetCRTAllocFailed( size_t nSize ) +{ + m_sMemoryAllocFailed = nSize; + + //MemAllocOOMError( nSize ); +} + +size_t CStdMemAlloc::MemoryAllocFailed() +{ + return m_sMemoryAllocFailed; +} + +#endif + +void ReserveBottomMemory() +{ + // If we are running a 64-bit build then reserve all addresses below the + // 4 GB line to push as many pointers as possible above the line. +#ifdef PLATFORM_WINDOWS_PC64 + // Avoid the cost of calling this multiple times. + static bool s_initialized = false; + if ( s_initialized ) + return; + s_initialized = true; + + // If AppVerifier is enabled then memory reservations get turned into committed + // memory in the working set. This means that ReserveBottomMemory() can end + // up adding almost 4 GB to the working set, which is a significant problem if + // you run many processes in parallel. Therefore, if vfbasics.dll (part of AppVerifier) + // is loaded, don't do the reservation. + HMODULE vfBasicsDLL = GetModuleHandle( "vfbasics.dll" ); + if ( vfBasicsDLL ) + return; + + // Start by reserving large blocks of memory. When those reservations + // have exhausted the bottom 4 GB then halve the size and try again. + // The granularity for reserving address space is 64 KB so if we wanted + // to reserve every single page we would need to continue down to 64 KB. + // However stopping at 1 MB is sufficient because it prevents the Windows + // heap (and dlmalloc and the small block heap) from grabbing address space + // from the bottom 4 GB, while still allowing Steam to allocate a few pages + // for setting up detours. + const size_t LOW_MEM_LINE = 0x100000000LL; + size_t totalReservation = 0; + size_t numVAllocs = 0; + size_t numHeapAllocs = 0; + for ( size_t blockSize = 256 * 1024 * 1024; blockSize >= 1024 * 1024; blockSize /= 2 ) + { + for (;;) + { + void* p = VirtualAlloc( 0, blockSize, MEM_RESERVE, PAGE_NOACCESS ); + if ( !p ) + break; + + if ( (size_t)p >= LOW_MEM_LINE ) + { + // We don't need this memory, so release it completely. + VirtualFree( p, 0, MEM_RELEASE ); + break; + } + + totalReservation += blockSize; + ++numVAllocs; + } + } + + // Now repeat the same process but making heap allocations, to use up the + // already committed heap blocks that are below the 4 GB line. Now we start + // with 64-KB allocations and proceed down to 16-byte allocations. + HANDLE heap = GetProcessHeap(); + for ( size_t blockSize = 64 * 1024; blockSize >= 16; blockSize /= 2 ) + { + for (;;) + { + void* p = HeapAlloc( heap, 0, blockSize ); + if ( !p ) + break; + + if ( (size_t)p >= LOW_MEM_LINE ) + { + // We don't need this memory, so release it completely. + HeapFree( heap, 0, p ); + break; + } + + totalReservation += blockSize; + ++numHeapAllocs; + } + } + + // Print diagnostics showing how many allocations we had to make in order to + // reserve all of low memory. In one test run it took 55 virtual allocs and + // 85 heap allocs. Note that since the process may have multiple heaps (each + // CRT seems to have its own) there is likely to be a few MB of address space + // that was previously reserved and is available to be handed out by some allocators. + //char buffer[1000]; + //sprintf_s( buffer, "Reserved %1.3f MB (%d vallocs, %d heap allocs) to keep allocations out of low-memory.\n", + // totalReservation / (1024 * 1024.0), (int)numVAllocs, (int)numHeapAllocs ); + // Can't use Msg here because it isn't necessarily initialized yet. + //OutputDebugString( buffer ); +#endif +} + +#endif // STEAM diff --git a/tier0/memstd.h b/tier0/memstd.h new file mode 100644 index 0000000..59ea7f7 --- /dev/null +++ b/tier0/memstd.h @@ -0,0 +1,292 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +//----------------------------------------------------------------------------- +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually +//----------------------------------------------------------------------------- +#include "pch_tier0.h" + +#if defined(_WIN32) +#if !defined(_X360) +#define WIN_32_LEAN_AND_MEAN +#include +#else +#undef Verify +#define _XBOX +#include +#undef _XBOX +#include "xbox/xbox_win32stubs.h" +#endif +#endif + +#include +#include +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "tier0/threadtools.h" +#include "tier0/tslist.h" +#include "mem_helpers.h" + +#pragma pack(4) + +#ifdef _X360 +#define USE_PHYSICAL_SMALL_BLOCK_HEAP 1 +#endif + + +// #define NO_SBH 1 + + +#define MIN_SBH_BLOCK 8 +#define MIN_SBH_ALIGN 8 +#define MAX_SBH_BLOCK 2048 +#define MAX_POOL_REGION (4*1024*1024) +#if !defined(_X360) +#define SBH_PAGE_SIZE (4*1024) +#define COMMIT_SIZE (16*SBH_PAGE_SIZE) +#else +#define SBH_PAGE_SIZE (64*1024) +#define COMMIT_SIZE (SBH_PAGE_SIZE) +#endif +#if _M_X64 +#define NUM_POOLS 34 +#else +#define NUM_POOLS 42 +#endif + +// SBH not enabled for LINUX right now. Unlike on Windows, we can't globally hook malloc. Well, +// we can and did in override_init_hook(), but that unfortunately causes all malloc functions +// to get hooked - including the nVidia driver, etc. And these hooks appear to happen after +// nVidia has alloc'd some memory and it crashes when they try to free that. +// So we need things to work without this global hook - which means we rely on memdbgon.h / memdbgoff.h. +// Unfortunately, that stuff always comes in source files after the headers are included, and +// that means any alloc calls in the header files call the real libc functions. It's a mess. +// I believe I've cleaned most of it up, and it appears to be working. However right now we are totally +// gated on other performance issues, and the SBH doesn't give us any win, so I've disabled it for now. +// Once those perf issues are worked out, it might make sense to do perf tests with SBH, libc, and tcmalloc. +// +//$ #if defined( _WIN32 ) || defined( _PS3 ) || defined( LINUX ) +#if defined( _WIN32 ) || defined( _PS3 ) +#define MEM_SBH_ENABLED 1 +#endif + +class ALIGN16 CSmallBlockPool +{ +public: + void Init( unsigned nBlockSize, byte *pBase, unsigned initialCommit = 0 ); + size_t GetBlockSize(); + bool IsOwner( void *p ); + void *Alloc(); + void Free( void *p ); + int CountFreeBlocks(); + int GetCommittedSize(); + int CountCommittedBlocks(); + int CountAllocatedBlocks(); + int Compact(); + +private: + + typedef TSLNodeBase_t FreeBlock_t; + class CFreeList : public CTSListBase + { + public: + void Push( void *p ) { CTSListBase::Push( (TSLNodeBase_t *)p ); } + }; + + CFreeList m_FreeList; + + unsigned m_nBlockSize; + + CInterlockedPtr m_pNextAlloc; + byte * m_pCommitLimit; + byte * m_pAllocLimit; + byte * m_pBase; + + CThreadFastMutex m_CommitMutex; +} ALIGN16_POST; + + +class ALIGN16 CSmallBlockHeap +{ +public: + CSmallBlockHeap(); + bool ShouldUse( size_t nBytes ); + bool IsOwner( void * p ); + void *Alloc( size_t nBytes ); + void *Realloc( void *p, size_t nBytes ); + void Free( void *p ); + size_t GetSize( void *p ); + void DumpStats( FILE *pFile = NULL ); + int Compact(); + +private: + CSmallBlockPool *FindPool( size_t nBytes ); + CSmallBlockPool *FindPool( void *p ); + + CSmallBlockPool *m_PoolLookup[MAX_SBH_BLOCK >> 2]; + CSmallBlockPool m_Pools[NUM_POOLS]; + byte *m_pBase; + byte *m_pLimit; +} ALIGN16_POST; + +#ifdef USE_PHYSICAL_SMALL_BLOCK_HEAP +#define BYTES_X360_SBH (32*1024*1024) +#define PAGESIZE_X360_SBH (64*1024) +class CX360SmallBlockPool +{ +public: + void Init( unsigned nBlockSize ); + size_t GetBlockSize(); + bool IsOwner( void *p ); + void *Alloc(); + void Free( void *p ); + int CountFreeBlocks(); + int GetCommittedSize(); + int CountCommittedBlocks(); + int CountAllocatedBlocks(); + + static CX360SmallBlockPool *FindPool( void *p ) + { + int index = (size_t)((byte *)p - gm_pPhysicalBase) / PAGESIZE_X360_SBH; + if ( index < 0 || index >= ARRAYSIZE(gm_AddressToPool) ) + return NULL; + return gm_AddressToPool[ index ]; + } + +private: + friend class CX360SmallBlockHeap; + + typedef TSLNodeBase_t FreeBlock_t; + class CFreeList : public CTSListBase + { + public: + void Push( void *p ) { CTSListBase::Push( (TSLNodeBase_t *)p ); } + }; + + CFreeList m_FreeList; + + unsigned m_nBlockSize; + unsigned m_CommittedSize; + + CInterlockedPtr m_pNextAlloc; + byte * m_pCurBlockEnd; + + CThreadFastMutex m_CommitMutex; + + static CX360SmallBlockPool *gm_AddressToPool[BYTES_X360_SBH/PAGESIZE_X360_SBH]; + + static byte *gm_pPhysicalBlock; + static byte *gm_pPhysicalBase; + static byte *gm_pPhysicalLimit; +}; + + +class CX360SmallBlockHeap +{ +public: + CX360SmallBlockHeap(); + bool ShouldUse( size_t nBytes ); + bool IsOwner( void * p ); + void *Alloc( size_t nBytes ); + void *Realloc( void *p, size_t nBytes ); + void Free( void *p ); + size_t GetSize( void *p ); + void DumpStats( FILE *pFile = NULL ); + + CSmallBlockHeap *GetStandardSBH(); + +private: + CX360SmallBlockPool *FindPool( size_t nBytes ); + CX360SmallBlockPool *FindPool( void *p ); + + CX360SmallBlockPool *m_PoolLookup[MAX_SBH_BLOCK >> 2]; + CX360SmallBlockPool m_Pools[NUM_POOLS]; +}; +#endif + + +class ALIGN16 CStdMemAlloc : public IMemAlloc +{ +public: + CStdMemAlloc() + : m_pfnFailHandler( DefaultFailHandler ), + m_sMemoryAllocFailed( (size_t)0 ) + { + // Make sure that we return 64-bit addresses in 64-bit builds. + ReserveBottomMemory(); + } + // Release versions + virtual void *Alloc( size_t nSize ); + virtual void *Realloc( void *pMem, size_t nSize ); + virtual void Free( void *pMem ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ); + + // Debug versions + virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ); + virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ); + virtual void Free( void *pMem, const char *pFileName, int nLine ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ); + + // Returns size of a particular allocation + virtual size_t GetSize( void *pMem ); + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo( const char *pFileName, int nLine ); + virtual void PopAllocDbgInfo(); + + virtual long CrtSetBreakAlloc( long lNewBreakAlloc ); + virtual int CrtSetReportMode( int nReportType, int nReportMode ); + virtual int CrtIsValidHeapPointer( const void *pMem ); + virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ); + virtual int CrtCheckMemory( void ); + virtual int CrtSetDbgFlag( int nNewFlag ); + virtual void CrtMemCheckpoint( _CrtMemState *pState ); + void* CrtSetReportFile( int nRptType, void* hFile ); + void* CrtSetReportHook( void* pfnNewHook ); + int CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ); + virtual int heapchk(); + + virtual void DumpStats(); + virtual void DumpStatsFileBase( char const *pchFileBase ); + virtual void GlobalMemoryStatus( size_t *pUsedMemory, size_t *pFreeMemory ); + + virtual bool IsDebugHeap() { return false; } + + virtual void GetActualDbgInfo( const char *&pFileName, int &nLine ) {} + virtual void RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) {} + virtual void RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) {} + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void CompactHeap(); + + virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ); + size_t CallAllocFailHandler( size_t nBytes ) { return (*m_pfnFailHandler)( nBytes); } + + virtual uint32 GetDebugInfoSize() { return 0; } + virtual void SaveDebugInfo( void *pvDebugInfo ) { } + virtual void RestoreDebugInfo( const void *pvDebugInfo ) {} + virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) {} + + static size_t DefaultFailHandler( size_t ); + void DumpBlockStats( void *p ) {} +#ifdef MEM_SBH_ENABLED + CSmallBlockHeap m_SmallBlockHeap; +#ifdef USE_PHYSICAL_SMALL_BLOCK_HEAP + CX360SmallBlockHeap m_LargePageSmallBlockHeap; +#endif +#endif + +#if defined( _MEMTEST ) + virtual void SetStatsExtraInfo( const char *pMapName, const char *pComment ); +#endif + + virtual size_t MemoryAllocFailed(); + + void SetCRTAllocFailed( size_t nMemSize ); + + MemAllocFailHandler_t m_pfnFailHandler; + size_t m_sMemoryAllocFailed; +} ALIGN16_POST; + + diff --git a/tier0/memvalidate.cpp b/tier0/memvalidate.cpp new file mode 100644 index 0000000..73b15cc --- /dev/null +++ b/tier0/memvalidate.cpp @@ -0,0 +1,485 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Memory allocation! +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" + +#ifndef STEAM + +#ifdef TIER0_VALIDATE_HEAP + +#include +#include "tier0/dbg.h" +#include "tier0/memalloc.h" +#include "mem_helpers.h" + +extern IMemAlloc *g_pActualAlloc; + +//----------------------------------------------------------------------------- +// NOTE! This should never be called directly from leaf code +// Just use new,delete,malloc,free etc. They will call into this eventually +//----------------------------------------------------------------------------- +class CValidateAlloc : public IMemAlloc +{ +public: + enum + { + HEAP_PREFIX_BUFFER_SIZE = 12, + HEAP_SUFFIX_BUFFER_SIZE = 8, + }; + + CValidateAlloc(); + + // Release versions + virtual void *Alloc( size_t nSize ); + virtual void *Realloc( void *pMem, size_t nSize ); + virtual void Free( void *pMem ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize ); + + // Debug versions + virtual void *Alloc( size_t nSize, const char *pFileName, int nLine ); + virtual void *Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ); + virtual void Free( void *pMem, const char *pFileName, int nLine ); + virtual void *Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ); + + // Returns size of a particular allocation + virtual size_t GetSize( void *pMem ); + + // Force file + line information for an allocation + virtual void PushAllocDbgInfo( const char *pFileName, int nLine ); + virtual void PopAllocDbgInfo(); + + virtual long CrtSetBreakAlloc( long lNewBreakAlloc ); + virtual int CrtSetReportMode( int nReportType, int nReportMode ); + virtual int CrtIsValidHeapPointer( const void *pMem ); + virtual int CrtIsValidPointer( const void *pMem, unsigned int size, int access ); + virtual int CrtCheckMemory( void ); + virtual int CrtSetDbgFlag( int nNewFlag ); + virtual void CrtMemCheckpoint( _CrtMemState *pState ); + void* CrtSetReportFile( int nRptType, void* hFile ); + void* CrtSetReportHook( void* pfnNewHook ); + int CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ); + virtual int heapchk(); + + virtual void DumpStats() {} + virtual void DumpStatsFileBase( char const *pchFileBase ) {} + + virtual bool IsDebugHeap() + { + return true; + } + + virtual int GetVersion() { return MEMALLOC_VERSION; } + + virtual void CompactHeap(); + virtual MemAllocFailHandler_t SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ); + + virtual uint32 GetDebugInfoSize() { return 0; } + virtual void SaveDebugInfo( void *pvDebugInfo ) { } + virtual void RestoreDebugInfo( const void *pvDebugInfo ) {} + virtual void InitDebugInfo( void *pvDebugInfo, const char *pchRootFileName, int nLine ) {} + +private: + struct HeapPrefix_t + { + HeapPrefix_t *m_pPrev; + HeapPrefix_t *m_pNext; + int m_nSize; + unsigned char m_Prefix[HEAP_PREFIX_BUFFER_SIZE]; + }; + + struct HeapSuffix_t + { + unsigned char m_Suffix[HEAP_SUFFIX_BUFFER_SIZE]; + }; + +private: + // Returns the actual debug info + void GetActualDbgInfo( const char *&pFileName, int &nLine ); + + // Updates stats + void RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ); + void RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ); + + HeapSuffix_t *Suffix( HeapPrefix_t *pPrefix ); + void *AllocationStart( HeapPrefix_t *pBase ); + HeapPrefix_t *PrefixFromAllocation( void *pAlloc ); + const HeapPrefix_t *PrefixFromAllocation( const void *pAlloc ); + + // Add to the list! + void AddToList( HeapPrefix_t *pHeap, int nSize ); + + // Remove from the list! + void RemoveFromList( HeapPrefix_t *pHeap ); + + // Validate the allocation + bool ValidateAllocation( HeapPrefix_t *pHeap ); + +private: + HeapPrefix_t *m_pFirstAllocation; + char m_pPrefixImage[HEAP_PREFIX_BUFFER_SIZE]; + char m_pSuffixImage[HEAP_SUFFIX_BUFFER_SIZE]; +}; + + +//----------------------------------------------------------------------------- +// Singleton... +//----------------------------------------------------------------------------- +static CValidateAlloc s_ValidateAlloc; +IMemAlloc *g_pMemAlloc = &s_ValidateAlloc; + + +//----------------------------------------------------------------------------- +// Constructor. +//----------------------------------------------------------------------------- +CValidateAlloc::CValidateAlloc() +{ + m_pFirstAllocation = 0; + memset( m_pPrefixImage, 0xBE, HEAP_PREFIX_BUFFER_SIZE ); + memset( m_pSuffixImage, 0xAF, HEAP_SUFFIX_BUFFER_SIZE ); +} + + +//----------------------------------------------------------------------------- +// Accessors... +//----------------------------------------------------------------------------- +inline CValidateAlloc::HeapSuffix_t *CValidateAlloc::Suffix( HeapPrefix_t *pPrefix ) +{ + return reinterpret_cast( (unsigned char*)( pPrefix + 1 ) + pPrefix->m_nSize ); +} + +inline void *CValidateAlloc::AllocationStart( HeapPrefix_t *pBase ) +{ + return static_cast( pBase + 1 ); +} + +inline CValidateAlloc::HeapPrefix_t *CValidateAlloc::PrefixFromAllocation( void *pAlloc ) +{ + if ( !pAlloc ) + return NULL; + + return ((HeapPrefix_t*)pAlloc) - 1; +} + +inline const CValidateAlloc::HeapPrefix_t *CValidateAlloc::PrefixFromAllocation( const void *pAlloc ) +{ + return ((const HeapPrefix_t*)pAlloc) - 1; +} + + +//----------------------------------------------------------------------------- +// Add to the list! +//----------------------------------------------------------------------------- +void CValidateAlloc::AddToList( HeapPrefix_t *pHeap, int nSize ) +{ + pHeap->m_pPrev = NULL; + pHeap->m_pNext = m_pFirstAllocation; + if ( m_pFirstAllocation ) + { + m_pFirstAllocation->m_pPrev = pHeap; + } + pHeap->m_nSize = nSize; + + m_pFirstAllocation = pHeap; + + HeapSuffix_t *pSuffix = Suffix( pHeap ); + memcpy( pHeap->m_Prefix, m_pPrefixImage, HEAP_PREFIX_BUFFER_SIZE ); + memcpy( pSuffix->m_Suffix, m_pSuffixImage, HEAP_SUFFIX_BUFFER_SIZE ); +} + + +//----------------------------------------------------------------------------- +// Remove from the list! +//----------------------------------------------------------------------------- +void CValidateAlloc::RemoveFromList( HeapPrefix_t *pHeap ) +{ + if ( !pHeap ) + return; + + ValidateAllocation( pHeap ); + if ( pHeap->m_pPrev ) + { + pHeap->m_pPrev->m_pNext = pHeap->m_pNext; + } + else + { + m_pFirstAllocation = pHeap->m_pNext; + } + + if ( pHeap->m_pNext ) + { + pHeap->m_pNext->m_pPrev = pHeap->m_pPrev; + } +} + + +//----------------------------------------------------------------------------- +// Validate the allocation +//----------------------------------------------------------------------------- +bool CValidateAlloc::ValidateAllocation( HeapPrefix_t *pHeap ) +{ + HeapSuffix_t *pSuffix = Suffix( pHeap ); + + bool bOk = true; + if ( memcmp( pHeap->m_Prefix, m_pPrefixImage, HEAP_PREFIX_BUFFER_SIZE ) ) + { + bOk = false; + } + + if ( memcmp( pSuffix->m_Suffix, m_pSuffixImage, HEAP_SUFFIX_BUFFER_SIZE ) ) + { + bOk = false; + } + + if ( !bOk ) + { + Warning("Memory trash detected in allocation %X!\n", (void*)(pHeap+1) ); + Assert( 0 ); + } + + return bOk; +} + +//----------------------------------------------------------------------------- +// Release versions +//----------------------------------------------------------------------------- +void *CValidateAlloc::Alloc( size_t nSize ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + HeapPrefix_t *pHeap = (HeapPrefix_t*)g_pActualAlloc->Alloc( nActualSize ); + AddToList( pHeap, nSize ); + return AllocationStart( pHeap ); +} + +void *CValidateAlloc::Realloc( void *pMem, size_t nSize ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Realloc( pHeap, nActualSize ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + +void CValidateAlloc::Free( void *pMem ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + g_pActualAlloc->Free( pHeap ); +} + +void *CValidateAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Expand_NoLongerSupported( pHeap, nActualSize ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + + +//----------------------------------------------------------------------------- +// Debug versions +//----------------------------------------------------------------------------- +void *CValidateAlloc::Alloc( size_t nSize, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + HeapPrefix_t *pHeap = (HeapPrefix_t*)g_pActualAlloc->Alloc( nActualSize, pFileName, nLine ); + AddToList( pHeap, nSize ); + return AllocationStart( pHeap ); +} + +void *CValidateAlloc::Realloc( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Realloc( pHeap, nActualSize, pFileName, nLine ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + +void CValidateAlloc::Free( void *pMem, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + g_pActualAlloc->Free( pHeap, pFileName, nLine ); +} + +void *CValidateAlloc::Expand_NoLongerSupported( void *pMem, size_t nSize, const char *pFileName, int nLine ) +{ + Assert( heapchk() == _HEAPOK ); + Assert( CrtCheckMemory() ); + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + RemoveFromList( pHeap ); + + int nActualSize = nSize + sizeof(HeapPrefix_t) + sizeof(HeapSuffix_t); + pHeap = (HeapPrefix_t*)g_pActualAlloc->Expand_NoLongerSupported( pHeap, nActualSize, pFileName, nLine ); + AddToList( pHeap, nSize ); + + return AllocationStart( pHeap ); +} + + +//----------------------------------------------------------------------------- +// Returns size of a particular allocation +//----------------------------------------------------------------------------- +size_t CValidateAlloc::GetSize( void *pMem ) +{ + if ( !pMem ) + return CalcHeapUsed(); + + HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + return pHeap->m_nSize; +} + + +//----------------------------------------------------------------------------- +// Force file + line information for an allocation +//----------------------------------------------------------------------------- +void CValidateAlloc::PushAllocDbgInfo( const char *pFileName, int nLine ) +{ + g_pActualAlloc->PushAllocDbgInfo( pFileName, nLine ); +} + +void CValidateAlloc::PopAllocDbgInfo() +{ + g_pActualAlloc->PopAllocDbgInfo( ); +} + +//----------------------------------------------------------------------------- +// FIXME: Remove when we make our own heap! Crt stuff we're currently using +//----------------------------------------------------------------------------- +long CValidateAlloc::CrtSetBreakAlloc( long lNewBreakAlloc ) +{ + return g_pActualAlloc->CrtSetBreakAlloc( lNewBreakAlloc ); +} + +int CValidateAlloc::CrtSetReportMode( int nReportType, int nReportMode ) +{ + return g_pActualAlloc->CrtSetReportMode( nReportType, nReportMode ); +} + +int CValidateAlloc::CrtIsValidHeapPointer( const void *pMem ) +{ + const HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + return g_pActualAlloc->CrtIsValidHeapPointer( pHeap ); +} + +int CValidateAlloc::CrtIsValidPointer( const void *pMem, unsigned int size, int access ) +{ + const HeapPrefix_t *pHeap = PrefixFromAllocation( pMem ); + return g_pActualAlloc->CrtIsValidPointer( pHeap, size, access ); +} + +int CValidateAlloc::CrtCheckMemory( void ) +{ + return g_pActualAlloc->CrtCheckMemory( ); +} + +int CValidateAlloc::CrtSetDbgFlag( int nNewFlag ) +{ + return g_pActualAlloc->CrtSetDbgFlag( nNewFlag ); +} + +void CValidateAlloc::CrtMemCheckpoint( _CrtMemState *pState ) +{ + g_pActualAlloc->CrtMemCheckpoint( pState ); +} + +void* CValidateAlloc::CrtSetReportFile( int nRptType, void* hFile ) +{ + return g_pActualAlloc->CrtSetReportFile( nRptType, hFile ); +} + +void* CValidateAlloc::CrtSetReportHook( void* pfnNewHook ) +{ + return g_pActualAlloc->CrtSetReportHook( pfnNewHook ); +} + +int CValidateAlloc::CrtDbgReport( int nRptType, const char * szFile, + int nLine, const char * szModule, const char * pMsg ) +{ + return g_pActualAlloc->CrtDbgReport( nRptType, szFile, nLine, szModule, pMsg ); +} + +int CValidateAlloc::heapchk() +{ + bool bOk = true; + + // Validate the heap + HeapPrefix_t *pHeap = m_pFirstAllocation; + for( pHeap = m_pFirstAllocation; pHeap; pHeap = pHeap->m_pNext ) + { + if ( !ValidateAllocation( pHeap ) ) + { + bOk = false; + } + } + +#ifdef _WIN32 + return bOk ? _HEAPOK : 0; +#elif POSIX + return bOk; +#else +#error +#endif +} + +// Returns the actual debug info +void CValidateAlloc::GetActualDbgInfo( const char *&pFileName, int &nLine ) +{ + g_pActualAlloc->GetActualDbgInfo( pFileName, nLine ); +} + +// Updates stats +void CValidateAlloc::RegisterAllocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + g_pActualAlloc->RegisterAllocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ); +} + +void CValidateAlloc::RegisterDeallocation( const char *pFileName, int nLine, int nLogicalSize, int nActualSize, unsigned nTime ) +{ + g_pActualAlloc->RegisterDeallocation( pFileName, nLine, nLogicalSize, nActualSize, nTime ); +} + +void CValidateAlloc::CompactHeap() +{ + g_pActualAlloc->CompactHeap(); +} + +MemAllocFailHandler_t CValidateAlloc::SetAllocFailHandler( MemAllocFailHandler_t pfnMemAllocFailHandler ) +{ + return g_pActualAlloc->SetAllocFailHandler( pfnMemAllocFailHandler ); +} + +#endif // TIER0_VALIDATE_HEAP + +#endif // STEAM diff --git a/tier0/minidump.cpp b/tier0/minidump.cpp new file mode 100644 index 0000000..a4c2f72 --- /dev/null +++ b/tier0/minidump.cpp @@ -0,0 +1,687 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "pch_tier0.h" + +#include "tier0/minidump.h" +#include "tier0/platform.h" + +#if defined( _WIN32 ) && !defined( _X360 ) + +#if _MSC_VER >= 1300 +#include "tier0/valve_off.h" +#define WIN_32_LEAN_AND_MEAN +#include + +#include + +#include + +// MiniDumpWriteDump() function declaration (so we can just get the function directly from windows) +typedef BOOL (WINAPI *MINIDUMPWRITEDUMP) + ( + HANDLE hProcess, + DWORD dwPid, + HANDLE hFile, + MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + + +// counter used to make sure minidump names are unique +static int g_nMinidumpsWritten = 0; + +// process-wide prefix to use for minidumps +static tchar g_rgchMinidumpFilenamePrefix[MAX_PATH]; + +// Process-wide comment to put into minidumps +static char g_rgchMinidumpComment[2048]; + +//----------------------------------------------------------------------------- +// Purpose: Creates a new file and dumps the exception info into it +// Input : uStructuredExceptionCode - windows exception code, unused. +// pExceptionInfo - call stack. +// minidumpType - type of minidump to write. +// ptchMinidumpFileNameBuffer - if not-NULL points to a writable tchar buffer +// of length at least _MAX_PATH to contain the name +// of the written minidump file on return. +//----------------------------------------------------------------------------- +bool WriteMiniDumpUsingExceptionInfo( + unsigned int uStructuredExceptionCode, + _EXCEPTION_POINTERS * pExceptionInfo, + int minidumpType, + const char *pszFilenameSuffix, + tchar *ptchMinidumpFileNameBuffer /* = NULL */ + ) +{ + if ( ptchMinidumpFileNameBuffer ) + { + *ptchMinidumpFileNameBuffer = tchar( 0 ); + } + + // get the function pointer directly so that we don't have to include the .lib, and that + // we can easily change it to using our own dll when this code is used on win98/ME/2K machines + HMODULE hDbgHelpDll = ::LoadLibrary( "DbgHelp.dll" ); + if ( !hDbgHelpDll ) + return false; + + bool bReturnValue = false; + MINIDUMPWRITEDUMP pfnMiniDumpWrite = (MINIDUMPWRITEDUMP) ::GetProcAddress( hDbgHelpDll, "MiniDumpWriteDump" ); + + if ( pfnMiniDumpWrite ) + { + // create a unique filename for the minidump based on the current time and module name + time_t currTime = ::time( NULL ); + struct tm * pTime = ::localtime( &currTime ); + ++g_nMinidumpsWritten; + + // If they didn't set a dump prefix, then set one for them using the module name + if ( g_rgchMinidumpFilenamePrefix[0] == TCHAR(0) ) + { + tchar rgchModuleName[MAX_PATH]; + #ifdef TCHAR_IS_WCHAR + ::GetModuleFileNameW( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) ); + #else + ::GetModuleFileName( NULL, rgchModuleName, sizeof(rgchModuleName) / sizeof(tchar) ); + #endif + + // strip off the rest of the path from the .exe name + tchar *pch = _tcsrchr( rgchModuleName, '.' ); + if ( pch ) + { + *pch = 0; + } + pch = _tcsrchr( rgchModuleName, '\\' ); + if ( pch ) + { + // move past the last slash + pch++; + } + else + { + pch = _T("unknown"); + } + strcpy( g_rgchMinidumpFilenamePrefix, pch ); + } + + + // can't use the normal string functions since we're in tier0 + tchar rgchFileName[MAX_PATH]; + _sntprintf( rgchFileName, sizeof(rgchFileName) / sizeof(tchar), + _T("%s_%d%02d%02d_%02d%02d%02d_%d%hs%hs.mdmp"), + g_rgchMinidumpFilenamePrefix, + pTime->tm_year + 1900, /* Year less 2000 */ + pTime->tm_mon + 1, /* month (0 - 11 : 0 = January) */ + pTime->tm_mday, /* day of month (1 - 31) */ + pTime->tm_hour, /* hour (0 - 23) */ + pTime->tm_min, /* minutes (0 - 59) */ + pTime->tm_sec, /* seconds (0 - 59) */ + g_nMinidumpsWritten, // ensures the filename is unique + ( pszFilenameSuffix != NULL ) ? "_" : "", + ( pszFilenameSuffix != NULL ) ? pszFilenameSuffix : "" + ); + // Ensure null-termination. + rgchFileName[ Q_ARRAYSIZE(rgchFileName) - 1 ] = 0; + + // Create directory, if our dump filename had a directory in it + for ( char *pSlash = rgchFileName ; *pSlash != '\0' ; ++pSlash ) + { + char c = *pSlash; + if ( c == '/' || c == '\\' ) + { + *pSlash = '\0'; + ::CreateDirectory( rgchFileName, NULL ); + *pSlash = c; + } + } + + BOOL bMinidumpResult = FALSE; +#ifdef TCHAR_IS_WCHAR + HANDLE hFile = ::CreateFileW( rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); +#else + HANDLE hFile = ::CreateFile( rgchFileName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); +#endif + + if ( hFile ) + { + // dump the exception information into the file + _MINIDUMP_EXCEPTION_INFORMATION ExInfo; + ExInfo.ThreadId = ::GetCurrentThreadId(); + ExInfo.ExceptionPointers = pExceptionInfo; + ExInfo.ClientPointers = FALSE; + + // Do we have a comment? + MINIDUMP_USER_STREAM_INFORMATION StreamInformationHeader; + MINIDUMP_USER_STREAM UserStreams[1]; + memset( &StreamInformationHeader, 0, sizeof(StreamInformationHeader) ); + StreamInformationHeader.UserStreamArray = UserStreams; + + if ( g_rgchMinidumpComment[0] != '\0' ) + { + MINIDUMP_USER_STREAM *pCommentStream = &UserStreams[StreamInformationHeader.UserStreamCount++]; + pCommentStream->Type = CommentStreamA; + pCommentStream->Buffer = g_rgchMinidumpComment; + pCommentStream->BufferSize = (ULONG)strlen(g_rgchMinidumpComment)+1; + } + + bMinidumpResult = (*pfnMiniDumpWrite)( ::GetCurrentProcess(), ::GetCurrentProcessId(), hFile, (MINIDUMP_TYPE)minidumpType, &ExInfo, &StreamInformationHeader, NULL ); + ::CloseHandle( hFile ); + + // Clear comment for next time + g_rgchMinidumpComment[0] = '\0'; + + if ( bMinidumpResult ) + { + bReturnValue = true; + + if ( ptchMinidumpFileNameBuffer ) + { + // Copy the file name from "pSrc = rgchFileName" into "pTgt = ptchMinidumpFileNameBuffer" + tchar *pTgt = ptchMinidumpFileNameBuffer; + tchar const *pSrc = rgchFileName; + while ( ( *( pTgt ++ ) = *( pSrc ++ ) ) != tchar( 0 ) ) + continue; + } + } + + // fall through to trying again + } + + // mark any failed minidump writes by renaming them + if ( !bMinidumpResult ) + { + tchar rgchFailedFileName[_MAX_PATH]; + _sntprintf( rgchFailedFileName, sizeof(rgchFailedFileName) / sizeof(tchar), "(failed)%s", rgchFileName ); + // Ensure null-termination. + rgchFailedFileName[ Q_ARRAYSIZE(rgchFailedFileName) - 1 ] = 0; + rename( rgchFileName, rgchFailedFileName ); + } + } + + ::FreeLibrary( hDbgHelpDll ); + + // call the log flush function if one is registered to try to flush any logs + //CallFlushLogFunc(); + + return bReturnValue; +} + + +void InternalWriteMiniDumpUsingExceptionInfo( unsigned int uStructuredExceptionCode, _EXCEPTION_POINTERS * pExceptionInfo, const char *pszFilenameSuffix ) +{ + // If this is is a real crash (not an assert or one we purposefully triggered), then try to write a full dump + // only do this on our GC (currently GC is 64-bit, so we can use a #define rather than some run-time switch +#ifdef _WIN64 + if ( uStructuredExceptionCode != EXCEPTION_BREAKPOINT ) + { + if ( WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, MiniDumpWithFullMemory, pszFilenameSuffix ) ) + { + return; + } + } +#endif + + // First try to write it with all the indirectly referenced memory (ie: a large file). + // If that doesn't work, then write a smaller one. + int iType = MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory; + if ( !WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, (MINIDUMP_TYPE)iType, pszFilenameSuffix ) ) + { + iType = MiniDumpWithDataSegs; + WriteMiniDumpUsingExceptionInfo( uStructuredExceptionCode, pExceptionInfo, (MINIDUMP_TYPE)iType, pszFilenameSuffix ); + } +} + +// minidump function to use +static FnMiniDump g_pfnWriteMiniDump = InternalWriteMiniDumpUsingExceptionInfo; + +//----------------------------------------------------------------------------- +// Purpose: Set a function to call which will write our minidump, overriding +// the default function +// Input : pfn - Pointer to minidump function to set +// Output : Previously set function +//----------------------------------------------------------------------------- +FnMiniDump SetMiniDumpFunction( FnMiniDump pfn ) +{ + FnMiniDump pfnTemp = g_pfnWriteMiniDump; + g_pfnWriteMiniDump = pfn; + return pfnTemp; +} + + +//----------------------------------------------------------------------------- +// Unhandled exceptions +//----------------------------------------------------------------------------- +static FnMiniDump g_UnhandledExceptionFunction; +static LONG STDCALL ValveUnhandledExceptionFilter( _EXCEPTION_POINTERS* pExceptionInfo ) +{ + uint uStructuredExceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode; + g_UnhandledExceptionFunction( uStructuredExceptionCode, pExceptionInfo, 0 ); + return EXCEPTION_CONTINUE_SEARCH; +} + +void MinidumpSetUnhandledExceptionFunction( FnMiniDump pfn ) +{ + g_UnhandledExceptionFunction = pfn; + SetUnhandledExceptionFilter( ValveUnhandledExceptionFilter ); +} + + +//----------------------------------------------------------------------------- +// Purpose: set prefix to use for filenames +//----------------------------------------------------------------------------- +void SetMinidumpFilenamePrefix( const char *pszPrefix ) +{ + #ifdef TCHAR_IS_WCHAR + mbstowcs( g_rgchMinidumpFilenamePrefix, pszPrefix, sizeof(g_rgchMinidumpFilenamePrefix) / sizeof(g_rgchMinidumpFilenamePrefix[0]) - 1 ); + #else + strncpy( g_rgchMinidumpFilenamePrefix, pszPrefix, sizeof(g_rgchMinidumpFilenamePrefix) / sizeof(g_rgchMinidumpFilenamePrefix[0]) - 1 ); + #endif +} + +//----------------------------------------------------------------------------- +// Purpose: set comment to put into minidumps +//----------------------------------------------------------------------------- +void SetMinidumpComment( const char *pszComment ) +{ + if ( pszComment == NULL ) + pszComment = ""; + strncpy( g_rgchMinidumpComment, pszComment, sizeof(g_rgchMinidumpComment) - 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: writes out a minidump from the current process +//----------------------------------------------------------------------------- +void WriteMiniDump( const char *pszFilenameSuffix ) +{ + // throw an exception so we can catch it and get the stack info + __try + { + ::RaiseException + ( + EXCEPTION_BREAKPOINT, // dwExceptionCode + EXCEPTION_NONCONTINUABLE, // dwExceptionFlags + 0, // nNumberOfArguments, + NULL // const ULONG_PTR* lpArguments + ); + + // Never get here (non-continuable exception) + } + // Write the minidump from inside the filter (GetExceptionInformation() is only + // valid in the filter) + __except ( g_pfnWriteMiniDump( EXCEPTION_BREAKPOINT, GetExceptionInformation(), pszFilenameSuffix ), EXCEPTION_EXECUTE_HANDLER ) + { + } +} + +DBG_OVERLOAD bool g_bInException = false; + +//----------------------------------------------------------------------------- +// Purpose: Catches and writes out any exception throw by the specified function. +// Input: pfn - Function to call within protective exception block +// pv - Void pointer to pass that function +//----------------------------------------------------------------------------- +void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] ) +{ + CatchAndWriteMiniDumpEx( pfn, argc, argv, k_ECatchAndWriteMiniDumpAbort ); +} + +// message types +enum ECatchAndWriteFunctionType +{ + k_eSCatchAndWriteFunctionTypeInvalid = 0, + k_eSCatchAndWriteFunctionTypeWMain = 1, // typedef void (*FnWMain)( int , tchar *[] ); + k_eSCatchAndWriteFunctionTypeWMainIntReg = 2, // typedef int (*FnWMainIntRet)( int , tchar *[] ); + k_eSCatchAndWriteFunctionTypeVoidPtr = 3, // typedef void (*FnVoidPtrFn)( void * ); +}; + +struct CatchAndWriteContext_t +{ + ECatchAndWriteFunctionType m_eType; + void *m_pfn; + int *m_pargc; + tchar ***m_pargv; + void **m_ppv; + ECatchAndWriteMinidumpAction m_eAction; + + void Set( ECatchAndWriteFunctionType eType, ECatchAndWriteMinidumpAction eAction, void *pfn, int *pargc, tchar **pargv[], void **ppv ) + { + m_eType = eType; + m_eAction = eAction; + m_pfn = pfn; + m_pargc = pargc; + m_pargv = pargv; + m_ppv = ppv; + + ErrorIfNot( m_pfn, ( "CatchAndWriteContext_t::Set w/o a function pointer!" ) ); + } + + int Invoke() + { + switch ( m_eType ) + { + default: + case k_eSCatchAndWriteFunctionTypeInvalid: + break; + case k_eSCatchAndWriteFunctionTypeWMain: + ErrorIfNot( m_pargc && m_pargv, ( "CatchAndWriteContext_t::Invoke with bogus argc/argv" ) ); + ((FnWMain)m_pfn)( *m_pargc, *m_pargv ); + break; + case k_eSCatchAndWriteFunctionTypeWMainIntReg: + ErrorIfNot( m_pargc && m_pargv, ( "CatchAndWriteContext_t::Invoke with bogus argc/argv" ) ); + return ((FnWMainIntRet)m_pfn)( *m_pargc, *m_pargv ); + case k_eSCatchAndWriteFunctionTypeVoidPtr: + ErrorIfNot( m_ppv, ( "CatchAndWriteContext_t::Invoke with bogus void *ptr" ) ); + ((FnVoidPtrFn)m_pfn)( *m_ppv ); + break; + } + + return 0; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: Catches and writes out any exception throw by the specified function +// Input: pfn - Function to call within protective exception block +// pv - Void pointer to pass that function +// eAction - Specifies what to do if it catches an exception +//----------------------------------------------------------------------------- +#if defined(_PS3) + +int CatchAndWriteMiniDump_Impl( CatchAndWriteContext_t &ctx ) +{ + // we dont handle minidumps on ps3 + return ctx.Invoke(); +} + +#else + +static const char *GetExceptionCodeName( unsigned long code ) +{ + switch ( code ) + { + case EXCEPTION_ACCESS_VIOLATION: return "accessviolation"; + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "arrayboundsexceeded"; + case EXCEPTION_BREAKPOINT: return "breakpoint"; + case EXCEPTION_DATATYPE_MISALIGNMENT: return "datatypemisalignment"; + case EXCEPTION_FLT_DENORMAL_OPERAND: return "fltdenormaloperand"; + case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "fltdividebyzero"; + case EXCEPTION_FLT_INEXACT_RESULT: return "fltinexactresult"; + case EXCEPTION_FLT_INVALID_OPERATION: return "fltinvalidoperation"; + case EXCEPTION_FLT_OVERFLOW: return "fltoverflow"; + case EXCEPTION_FLT_STACK_CHECK: return "fltstackcheck"; + case EXCEPTION_FLT_UNDERFLOW: return "fltunderflow"; + case EXCEPTION_INT_DIVIDE_BY_ZERO: return "intdividebyzero"; + case EXCEPTION_INT_OVERFLOW: return "intoverflow"; + case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "noncontinuableexception"; + case EXCEPTION_PRIV_INSTRUCTION: return "privinstruction"; + case EXCEPTION_SINGLE_STEP: return "singlestep"; + } + + // Unknown exception + return "crash"; +} + +int CatchAndWriteMiniDump_Impl( CatchAndWriteContext_t &ctx ) +{ + // Sorry, this is the only action currently implemented! + Assert( ctx.m_eAction == k_ECatchAndWriteMiniDumpAbort ); + + if ( Plat_IsInDebugSession() ) + { + // don't mask exceptions when running in the debugger + return ctx.Invoke(); + } + +// g_DumpHelper.Init(); + +// Win32 code gets to use a special handler +#if defined( _WIN32 ) + __try + { + return ctx.Invoke(); + } + __except ( g_pfnWriteMiniDump( GetExceptionCode(), GetExceptionInformation(), GetExceptionCodeName( GetExceptionCode() ) ), EXCEPTION_EXECUTE_HANDLER ) + { + TerminateProcess( GetCurrentProcess(), EXIT_FAILURE ); // die, die RIGHT NOW! (don't call exit() so destructors will not get run) + } + + // if we get here, we definitely are not in an exception handler + g_bInException = false; + + return 0; +#else +// if ( ctx.m_pargv != 0 ) +// { +// g_DumpHelper.ComputeExeNameFromArgv0( (*ctx.m_pargv)[ 0 ] ); +// } +// +// ICrashHandler *handler = g_DumpHelper.GetHandlerAPI(); +// CCrashHandlerScope scope( handler, g_DumpHelper.GetProduct(), g_DumpHelper.GetVersion(), g_DumpHelper.GetBuildID(), false ); +// if ( handler ) +// handler->SetSteamID( g_DumpHelper.GetSteamID() ); + + return ctx.Invoke(); +#endif +} + +#endif // _PS3 + +//----------------------------------------------------------------------------- +// Purpose: Catches and writes out any exception throw by the specified function +// Input: pfn - Function to call within protective exception block +// pv - Void pointer to pass that function +// eAction - Specifies what to do if it catches an exception +//----------------------------------------------------------------------------- +void CatchAndWriteMiniDumpEx( FnWMain pfn, int argc, tchar *argv[], ECatchAndWriteMinidumpAction eAction ) +{ + CatchAndWriteContext_t ctx; + ctx.Set( k_eSCatchAndWriteFunctionTypeWMain, eAction, (void *)pfn, &argc, &argv, NULL ); + CatchAndWriteMiniDump_Impl( ctx ); +} + +//----------------------------------------------------------------------------- +// Purpose: Catches and writes out any exception throw by the specified function +// Input: pfn - Function to call within protective exception block +// pv - Void pointer to pass that function +// eAction - Specifies what to do if it catches an exception +//----------------------------------------------------------------------------- +int CatchAndWriteMiniDumpExReturnsInt( FnWMainIntRet pfn, int argc, tchar *argv[], ECatchAndWriteMinidumpAction eAction ) +{ + CatchAndWriteContext_t ctx; + ctx.Set( k_eSCatchAndWriteFunctionTypeWMainIntReg, eAction, (void *)pfn, &argc, &argv, NULL ); + return CatchAndWriteMiniDump_Impl( ctx ); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Catches and writes out any exception throw by the specified function +// Input: pfn - Function to call within protective exception block +// pv - Void pointer to pass that function +// eAction - Specifies what to do if it catches an exception +//----------------------------------------------------------------------------- +void CatchAndWriteMiniDumpExForVoidPtrFn( FnVoidPtrFn pfn, void *pv, ECatchAndWriteMinidumpAction eAction ) +{ + CatchAndWriteContext_t ctx; + ctx.Set( k_eSCatchAndWriteFunctionTypeVoidPtr, eAction, (void *)pfn, NULL, NULL, &pv ); + CatchAndWriteMiniDump_Impl( ctx ); +} + +//----------------------------------------------------------------------------- +// Purpose: Catches and writes out any exception throw by the specified function +// Input: pfn - Function to call within protective exception block +// pv - Void pointer to pass that function +// bExitQuietly - If true (for client) just exit after mindump, with no visible error for user +// If false, re-throws. +//----------------------------------------------------------------------------- +void CatchAndWriteMiniDumpForVoidPtrFn( FnVoidPtrFn pfn, void *pv, bool bExitQuietly ) +{ + return CatchAndWriteMiniDumpExForVoidPtrFn( pfn, pv, bExitQuietly ? k_ECatchAndWriteMiniDumpAbort : k_ECatchAndWriteMiniDumpReThrow ); +} + + +/* +Call this function to ensure that your program actually crashes when it crashes. + +Oh my god. + +When 64-bit Windows came out it turns out that it wasn't possible to throw +and catch exceptions from user-mode, through kernel-mode, and back to user +mode. Therefore, for crashes that happen in kernel callbacks such as Window +procs Microsoft had to decide either to always crash when an exception +is thrown (including an SEH such as an access violation) or else always silently +swallow the exception. + +They chose badly. + +Therefore, for the last five or so years, programs on 64-bit Windows have been +silently swallowing *some* exceptions. As a concrete example, consider this code: + + case WM_PAINT: + { + hdc = BeginPaint(hWnd, &ps); + char* p = new char; + *(int*)0 = 0; + delete p; + EndPaint(hWnd, &ps); + } + break; + +It's in a WindowProc handling a paint message so it will generally be called from +kernel mode. Therefore the crash in the middle of it is, by default, 'handled' for +us. The "delete p;" and EndPaint() never happen. This means that the process is +left in an indeterminate state. It also means that our error reporting never sees +the exception. It is effectively as though there is a __try/__except handler at the +kernel boundary and any crashes cause the stack to be unwound (without destructors +being run) to the kernel boundary where execution continues. + +Charming. + +The fix is to use the Get/SetProcessUserModeExceptionPolicy API to tell Windows +that we don't want to struggle on after crashing. + +For more scary details see this article. It actually suggests using the compatibility +manifest, but that does not appear to work. +http://blog.paulbetts.org/index.php/2010/07/20/the-case-of-the-disappearing-onload-exception-user-mode-callback-exceptions-in-x64/ +*/ +void EnableCrashingOnCrashes() +{ + typedef BOOL (WINAPI *tGetProcessUserModeExceptionPolicy)(LPDWORD lpFlags); + typedef BOOL (WINAPI *tSetProcessUserModeExceptionPolicy)(DWORD dwFlags); + #define PROCESS_CALLBACK_FILTER_ENABLED 0x1 + + HMODULE kernel32 = LoadLibraryA("kernel32.dll"); + tGetProcessUserModeExceptionPolicy pGetProcessUserModeExceptionPolicy = (tGetProcessUserModeExceptionPolicy)GetProcAddress(kernel32, "GetProcessUserModeExceptionPolicy"); + tSetProcessUserModeExceptionPolicy pSetProcessUserModeExceptionPolicy = (tSetProcessUserModeExceptionPolicy)GetProcAddress(kernel32, "SetProcessUserModeExceptionPolicy"); + if (pGetProcessUserModeExceptionPolicy && pSetProcessUserModeExceptionPolicy) + { + DWORD dwFlags; + if (pGetProcessUserModeExceptionPolicy(&dwFlags)) + { + pSetProcessUserModeExceptionPolicy(dwFlags & ~PROCESS_CALLBACK_FILTER_ENABLED); // turn off bit 1 + } + } +} + +#else + +PLATFORM_INTERFACE void WriteMiniDump( const char *pszFilenameSuffix ) +{ +} + +PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] ) +{ + pfn( argc, argv ); +} + +#endif +#elif defined(_X360 ) +PLATFORM_INTERFACE void WriteMiniDump( const char *pszFilenameSuffix ) +{ + DmCrashDump(false); +} + +#else // !_WIN32 +#include "tier0/minidump.h" + +PLATFORM_INTERFACE void WriteMiniDump( const char *pszFilenameSuffix ) +{ +} + +PLATFORM_INTERFACE void CatchAndWriteMiniDump( FnWMain pfn, int argc, tchar *argv[] ) +{ + pfn( argc, argv ); +} + +#endif + +// User minidump stream info comment strings. +// +// Single header string of 512 bytes set via MinidumpUserStreamInfoSetHeader. +static char g_UserStreamInfoHeader[ 512 ]; +// Array of 32 round robin 128 byte strings set via MinidumpUserStreamInfoAppend. +static char g_UserStreamInfo[ 64 ][ 128 ]; +static int g_UserStreamInfoIndex = 0; + +// Set the single g_UserStreamInfoHeader string. +void MinidumpUserStreamInfoSetHeader( const char *pFormat, ... ) +{ + va_list marker; + + va_start( marker, pFormat ); + _vsnprintf( g_UserStreamInfoHeader, ARRAYSIZE( g_UserStreamInfoHeader ), pFormat, marker ); + g_UserStreamInfoHeader[ ARRAYSIZE( g_UserStreamInfoHeader ) - 1 ] = 0; + va_end( marker ); +} + +// Set the next comment in the g_UserStreamInfo array. +void MinidumpUserStreamInfoAppend( const char *pFormat, ... ) +{ + va_list marker; + char *pData = g_UserStreamInfo[ g_UserStreamInfoIndex ]; + const int DataSize = ARRAYSIZE( g_UserStreamInfo[ g_UserStreamInfoIndex ] ); + + // Add tick count just so we have a general idea of when this event happened. + _snprintf( pData, DataSize, "[%x]", Plat_MSTime() ); + pData[ DataSize - 1 ] = 0; + size_t HeaderLen = strlen( pData ); + + va_start( marker, pFormat ); + _vsnprintf( pData + HeaderLen, DataSize - HeaderLen, pFormat, marker ); + pData[ DataSize - 1 ] = 0; + va_end( marker ); + + // Bump up index, and go back to 0 if we've hit the end. + g_UserStreamInfoIndex++; + if( g_UserStreamInfoIndex >= ARRAYSIZE( g_UserStreamInfo ) ) + { + g_UserStreamInfoIndex = 0; + } +} + +// Retrieve the string given the Index. +// Index 0: header string +// Index 1+: comment string +// Returns NULL when you've reached the end of the comment string array +// Empty strings ("\0") can be returned if comment hasn't been set +const char *MinidumpUserStreamInfoGet( int Index ) +{ + if( ( Index < 0 ) || ( Index >= (ARRAYSIZE( g_UserStreamInfo ) + 1) ) ) //+1 because we map 0 to the header + return NULL; + + if( Index == 0 ) + return g_UserStreamInfoHeader; + + Index = ( (Index + (ARRAYSIZE( g_UserStreamInfo ) - 1)) + //subtract 1 in a way that circularly wraps. Since 0 maps to the header, the comment indices are 1 based + g_UserStreamInfoIndex ) //start with our oldest comment + % ARRAYSIZE( g_UserStreamInfo ); //circular buffer wrapping + + return g_UserStreamInfo[ Index ]; +} + + diff --git a/tier0/pch_tier0.cpp b/tier0/pch_tier0.cpp new file mode 100644 index 0000000..24bd48d --- /dev/null +++ b/tier0/pch_tier0.cpp @@ -0,0 +1,9 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include "pch_tier0.h" \ No newline at end of file diff --git a/tier0/pch_tier0.h b/tier0/pch_tier0.h new file mode 100644 index 0000000..ad06d22 --- /dev/null +++ b/tier0/pch_tier0.h @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// + + +#if defined(_WIN32) && !defined(_X360) +#define WIN32_LEAN_AND_MEAN +// 0x0601 gives us access to Windows 7 features. We have to be careful not to +// depend on these features because we still support Windows XP. +#define _WIN32_WINNT 0x0601 +#include +#include +#endif + +// tier0 +#include "tier0/basetypes.h" +#include "tier0/dbgflag.h" +#include "tier0/dbg.h" +#ifdef STEAM +#include "tier0/memhook.h" +#endif +#include "tier0/validator.h" + +// First include standard libraries +#include "tier0/valve_off.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tier0/valve_minmax_off.h" // GCC 4.2.2 headers screw up our min/max defs. +#include +#include "tier0/valve_minmax_on.h" // GCC 4.2.2 headers screw up our min/max defs. + +#include +#ifdef POSIX +#include +#include +#define _MAX_PATH PATH_MAX +#endif + +#include "tier0/valve_on.h" + + + + + + + diff --git a/tier0/platform.cpp b/tier0/platform.cpp new file mode 100644 index 0000000..9fbd34c --- /dev/null +++ b/tier0/platform.cpp @@ -0,0 +1,291 @@ +//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "pch_tier0.h" +#include + +#ifdef _LINUX +#include "platform_linux.cpp" +#else +#if defined(_WIN32) && !defined(_X360) +#define WINDOWS_LEAN_AND_MEAN +#define _WIN32_WINNT 0x0502 +#include +#endif +#include +#include "tier0/platform.h" +#ifdef _X360 +#include "xbox/xbox_console.h" +#endif // _X360 +#ifndef _X360 +#include "tier0/vcrmode.h" +#endif // _X360 +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) +#include "tier0/memalloc.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" +#endif + +#ifndef _X360 +extern VCRMode_t g_VCRMode; +#endif +static LARGE_INTEGER g_PerformanceFrequency; +static LARGE_INTEGER g_MSPerformanceFrequency; +static LARGE_INTEGER g_ClockStart; + +static void InitTime() +{ + if (!g_PerformanceFrequency.QuadPart) + { + QueryPerformanceFrequency(&g_PerformanceFrequency); + g_MSPerformanceFrequency.QuadPart = g_PerformanceFrequency.QuadPart / 1000; + QueryPerformanceCounter(&g_ClockStart); + } +} + +double Plat_FloatTime() +{ + InitTime(); + + LARGE_INTEGER CurrentTime; + + QueryPerformanceCounter(&CurrentTime); + + double fRawSeconds = (double)(CurrentTime.QuadPart - g_ClockStart.QuadPart) / (double)(g_PerformanceFrequency.QuadPart); + + return fRawSeconds; +} + +unsigned int Plat_MSTime() +{ + InitTime(); + + LARGE_INTEGER CurrentTime; + + QueryPerformanceCounter(&CurrentTime); + + return (unsigned long)((CurrentTime.QuadPart - g_ClockStart.QuadPart) / g_MSPerformanceFrequency.QuadPart); +} + +struct tm* Plat_localtime(const time_t* timep, struct tm* result) +{ + result = localtime(timep); + return result; +} + +void GetCurrentDate(int* pDay, int* pMonth, int* pYear) +{ + struct tm* pNewTime; + time_t long_time; + + time(&long_time); /* Get time as long integer. */ + pNewTime = localtime(&long_time); /* Convert to local time. */ + + *pDay = pNewTime->tm_mday; + *pMonth = pNewTime->tm_mon + 1; + *pYear = pNewTime->tm_year + 1900; +} + +bool vtune(bool resume) +{ +#ifndef _X360 + static bool bInitialized = false; + static void(__cdecl * VTResume)(void) = NULL; + static void(__cdecl * VTPause) (void) = NULL; + + // Grab the Pause and Resume function pointers from the VTune DLL the first time through: + if (!bInitialized) + { + bInitialized = true; + + HINSTANCE pVTuneDLL = LoadLibrary("vtuneapi.dll"); + + if (pVTuneDLL) + { + VTResume = (void(__cdecl*)())GetProcAddress(pVTuneDLL, "VTResume"); + VTPause = (void(__cdecl*)())GetProcAddress(pVTuneDLL, "VTPause"); + } + } + + // Call the appropriate function, as indicated by the argument: + if (resume && VTResume) + { + VTResume(); + return true; + + } + else if (!resume && VTPause) + { + VTPause(); + return true; + } +#endif + return false; +} + +bool Plat_IsInDebugSession() +{ +#if defined( _WIN32 ) && !defined( _X360 ) + return (IsDebuggerPresent() != 0); +#elif defined( _WIN32 ) && defined( _X360 ) + return (XBX_IsDebuggerPresent() != 0); +#else + return false; +#endif +} + +void Plat_DebugString(const char* psz) +{ +#if defined( _WIN32 ) && !defined( _X360 ) + ::OutputDebugStringA(psz); +#elif defined( _WIN32 ) && defined( _X360 ) + XBX_OutputDebugString(psz); +#endif +} + + +const tchar* Plat_GetCommandLine() +{ +#ifdef TCHAR_IS_WCHAR + return GetCommandLineW(); +#else + return GetCommandLine(); +#endif +} + +const char* Plat_GetCommandLineA() +{ + return GetCommandLineA(); +} + +// -------------------------------------------------------------------------------------------------- // +// Memory stuff. +// +// DEPRECATED. Still here to support binary back compatability of tier0.dll +// +// -------------------------------------------------------------------------------------------------- // +#ifndef _X360 +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + +typedef void (*Plat_AllocErrorFn)(unsigned long size); + +void Plat_DefaultAllocErrorFn(unsigned long size) +{ +} + +Plat_AllocErrorFn g_AllocError = Plat_DefaultAllocErrorFn; +#endif + +#ifndef _X360 +CRITICAL_SECTION g_AllocCS; +class CAllocCSInit +{ +public: + CAllocCSInit() + { + InitializeCriticalSection(&g_AllocCS); + } +} g_AllocCSInit; +#endif + +#ifndef _X360 +PLATFORM_INTERFACE void* Plat_Alloc(unsigned long size) +{ + EnterCriticalSection(&g_AllocCS); +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + void* pRet = g_pMemAlloc->Alloc(size); +#else + void* pRet = malloc(size); +#endif + LeaveCriticalSection(&g_AllocCS); + if (pRet) + { + return pRet; + } + else + { +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_AllocError(size); +#endif + return 0; + } +} +#endif + +#ifndef _X360 +PLATFORM_INTERFACE void* Plat_Realloc(void* ptr, unsigned long size) +{ + EnterCriticalSection(&g_AllocCS); +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + void* pRet = g_pMemAlloc->Realloc(ptr, size); +#else + void* pRet = realloc(ptr, size); +#endif + LeaveCriticalSection(&g_AllocCS); + if (pRet) + { + return pRet; + } + else + { +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_AllocError(size); +#endif + return 0; + } +} +#endif + +#ifndef _X360 +PLATFORM_INTERFACE void Plat_Free(void* ptr) +{ + EnterCriticalSection(&g_AllocCS); +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) + g_pMemAlloc->Free(ptr); +#else + free(ptr); +#endif + LeaveCriticalSection(&g_AllocCS); +} +#endif + +#ifndef _X360 +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) +PLATFORM_INTERFACE void Plat_SetAllocErrorFn(Plat_AllocErrorFn fn) +{ + g_AllocError = fn; +} +#endif +#endif + +#endif + +//stubs +// watchdog timer support +PLATFORM_INTERFACE void Plat_BeginWatchdogTimer(int nSecs) +{ +} + +PLATFORM_INTERFACE void Plat_EndWatchdogTimer(void) +{ +} + +PLATFORM_INTERFACE void Plat_SetWatchdogHandlerFunction(Plat_WatchDogHandlerFunction_t function) +{ +} + +void Plat_SetBenchmarkMode(bool bBenchmark) +{ +} + +PLATFORM_INTERFACE bool Plat_IsInBenchmarkMode() +{ + return false; +} + +#endif // _LINUX \ No newline at end of file diff --git a/tier0/platform_posix.cpp b/tier0/platform_posix.cpp new file mode 100644 index 0000000..ba2783a --- /dev/null +++ b/tier0/platform_posix.cpp @@ -0,0 +1,1083 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "tier0/platform.h" +#include "tier0/vcrmode.h" +#include "tier0/memalloc.h" +#include "tier0/dbg.h" +#include +#include + +#include +#include +#include + +#ifdef OSX +#include +#include +#include +#include +#include +#include +#endif +#ifdef LINUX +#include +#include +#endif + +#include "tier0/memdbgon.h" +// Benchmark mode uses this heavy-handed method + +// *** WARNING ***. On Linux gettimeofday returns the system's best guess at +// actual wall clock time and this can go backwards. You need to use +// clock_gettime( CLOCK_MONOTONIC ... ) if this isn't what you want. + +// If you want to try using rdtsc for Plat_FloatTime(), enable USE_RDTSC_FOR_FLOATTIME: +// +// Make sure you know what you're doing. This was disabled due to the long startup time, and +// in our testing, even though constant_tsc was set, we couldn't rely on the +// max frequency result returned from CalculateCPUFreq() (ie /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq). +// +// #define USE_RDTSC_FOR_FLOATTIME + +extern VCRMode_t g_VCRMode; + +static bool g_bBenchmarkMode = false; +static double g_FakeBenchmarkTime = 0; +static double g_FakeBenchmarkTimeInc = 1.0 / 66.0; + +#ifdef USE_RDTSC_FOR_FLOATTIME + +static bool s_bTimeInitted; +static bool s_bUseRDTSC; +static uint64 s_nRDTSCBase; +static float s_flRDTSCToMicroSeconds; +static double s_flRDTSCScale; + +#endif // USE_RDTSC_FOR_FLOATTIME + +bool Plat_IsInBenchmarkMode() +{ + return g_bBenchmarkMode; +} + +void Plat_SetBenchmarkMode( bool bBenchmark ) +{ + g_bBenchmarkMode = bBenchmark; +} + + +#define N_ITERATIONS_OF_RDTSC_TEST_TO_RUN 5 // should be odd +#define TEST_RDTSC_FLOATTIME 0 + +size_t ApproximateProcessMemoryUsage( void ) +{ +/* +From http://man7.org/linux/man-pages/man5/proc.5.html: + /proc/[pid]/statm + Provides information about memory usage, measured in pages. + The columns are: + + size (1) total program size + (same as VmSize in /proc/[pid]/status) + resident (2) resident set size + (same as VmRSS in /proc/[pid]/status) + share (3) shared pages (i.e., backed by a file) + text (4) text (code) + lib (5) library (unused in Linux 2.6) + data (6) data + stack + dt (7) dirty pages (unused in Linux 2.6) +*/ + +// This returns the resident memory size (RES column in 'top') in bytes. + size_t nRet = 0; + FILE *pFile = fopen( "/proc/self/statm", "r" ); + if ( pFile ) + { + size_t nSize, nResident, nShare, nText, nLib_Unused, nDataPlusStack, nDt_Unused; + if ( fscanf( pFile, "%zu %zu %zu %zu %zu %zu %zu", &nSize, &nResident, &nShare, &nText, &nLib_Unused, &nDataPlusStack, &nDt_Unused ) >= 2 ) + { + nRet = 4096 * nResident; + } + fclose( pFile ); + } + return nRet; +} + +#ifdef USE_RDTSC_FOR_FLOATTIME + +static void InitTimeSystem( void ) +{ + s_bTimeInitted = true; + + // now, see if we can use rdtsc instead. If this is one of the chips with a separate constant clock for rdtsc, we can + FILE *pCpuInfo = fopen( "/proc/cpuinfo", "r" ); + if ( pCpuInfo ) + { + bool bAnyBadCores = false; + char lbuf[2048]; + while( fgets( lbuf, sizeof( lbuf ), pCpuInfo ) ) + { + if ( memcmp( lbuf, "flags", 4 ) == 0 ) + { + if ( ! strstr( lbuf, "constant_tsc" ) ) + { + bAnyBadCores = true; + break; + } + } + } + fclose( pCpuInfo ); + if ( ! bAnyBadCores ) + { + // this system appears to have the proper cpu setup to use rdtsc from reliable timing. Let's either read the cpu frequency from an + // environment variable, or time it ourselves + char const *pEnv = getenv( "RDTSC_FREQUENCY" ); + if ( pEnv ) + { + // the environment variable is allowed to hold either a benchmark result, or a string such as "disable" + if ( pEnv && ( ( pEnv[0] > '9' ) || ( pEnv[0] < '0' ) ) ) + return; // leave rdtsc disabled + // the variable holds the number of ticks per microsecond + s_flRDTSCToMicroSeconds = atof( pEnv ); + // sanity check + if ( s_flRDTSCToMicroSeconds > 1.0 ) + { + s_bUseRDTSC = true; + s_flRDTSCScale = 1.0 / ( 1000.0 * 1000.0 * s_flRDTSCToMicroSeconds ); + s_nRDTSCBase = Plat_Rdtsc(); + return; + } + } + else + { + printf( "Running a benchmark to measure system clock frequency...\n" ); + // run n iterations and use the median + double flRDTSCToMicroSeconds[N_ITERATIONS_OF_RDTSC_TEST_TO_RUN]; + for( int i = 0; i < ARRAYSIZE( flRDTSCToMicroSeconds ) ; i++ ) + { + uint64 stime = Plat_Rdtsc(); + struct timeval stimeval; + gettimeofday( &stimeval, NULL ); + sleep( 1 ); + uint64 etime = Plat_Rdtsc() - stime; + struct timeval etimeval; + gettimeofday( &etimeval, NULL ); + // subtract timevals to get elapsed microseconds + struct timeval elapsedtimeval; + timersub( &etimeval, &stimeval, &elapsedtimeval ); + uint64 nUs = 1000000 * elapsedtimeval.tv_sec + elapsedtimeval.tv_usec; + flRDTSCToMicroSeconds[ i ] = ( etime / nUs ); + } + std::make_heap( flRDTSCToMicroSeconds, flRDTSCToMicroSeconds + ARRAYSIZE( flRDTSCToMicroSeconds ) - 1 ); + std::sort_heap( flRDTSCToMicroSeconds, flRDTSCToMicroSeconds + ARRAYSIZE( flRDTSCToMicroSeconds ) - 1 ); + s_flRDTSCToMicroSeconds = flRDTSCToMicroSeconds[ARRAYSIZE( flRDTSCToMicroSeconds ) / 2 ]; + s_flRDTSCScale = 1.0 / ( 1000.0 * 1000.0 * s_flRDTSCToMicroSeconds ); + printf( "Finished RDTSC test. To prevent the startup delay from this benchmark, set the environment variable RDTSC_FREQUENCY to %f on this system." + " This value is dependent upon the CPU clock speed and architecture and should be determined separately for each server. The use of this mechanism" + " for timing can be disabled by setting RDTSC_FREQUENCY to 'disabled'.\n", + s_flRDTSCToMicroSeconds ); + s_nRDTSCBase = Plat_Rdtsc(); + s_bUseRDTSC = true; +#if TEST_RDTSC_FLOATTIME + printf( "RDTSC test results:\n" ); + for( int i = 0; i < ARRAYSIZE( flRDTSCToMicroSeconds ); i++ ) + printf(" [%d] = %f\n", i, flRDTSCToMicroSeconds[i] ); + printf( "scale factor = %f\n", s_flRDTSCScale ); + uint64 srdtsc_time = Plat_Rdtsc(); + for( int i = 0; i < 1000 * 1000 * 10; i++ ) + { + float p = Plat_FloatTime(); + } + printf( "slow = %lld\n", Plat_Rdtsc() - srdtsc_time ); + // now, run a benchmark to see how much this optimization buys us + srdtsc_time = Plat_Rdtsc(); + for( int i = 0; i < 1000 * 1000 * 10; i++ ) + { + float p = Plat_FloatTime(); + } + printf( "sfast = %lld\n", Plat_Rdtsc() - srdtsc_time ); +#endif + } + } + } +} + +static FORCEINLINE void TestTimeSystem( void ) +{ +#if TEST_RDTSC_FLOATTIME + // now, test that plat_float time actually works + for( int t = 0 ; t < 5; t++ ) + { + float flStartT = Plat_FloatTime(); + struct timeval stime; + gettimeofday( &stime, NULL ); + sleep( 5 ); + float flElapsedT = Plat_FloatTime() - flStartT; + struct timeval etime; + gettimeofday( &etime, NULL ); + struct timeval dtime; + timersub( &etime, &stime, &dtime ); + printf( " plat_float time says %f elapsed. gettimeofday says %f\n", + flElapsedT, dtime.tv_sec + dtime.tv_usec / 1000000.0 ); + } +#endif +} + +#endif // USE_RDTSC_FOR_FLOATTIME + +double Plat_FloatTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return g_FakeBenchmarkTime; + } + +#ifdef OSX + // OSX + static uint64 start_time = 0; + static mach_timebase_info_data_t sTimebaseInfo; + static double conversion = 0.0; + + if ( !start_time ) + { + start_time = mach_absolute_time(); + mach_timebase_info(&sTimebaseInfo); + conversion = 1e-9 * (double) sTimebaseInfo.numer / (double) sTimebaseInfo.denom; + } + + uint64 now = mach_absolute_time(); + + return ( now - start_time ) * conversion; +#else + // Linux + static struct timespec start_time = { 0, 0 }; + static bool bInitialized = false; + + if ( !bInitialized ) + { + bInitialized = true; + clock_gettime( CLOCK_MONOTONIC, &start_time ); + } + + struct timespec now; + clock_gettime( CLOCK_MONOTONIC, &now ); + + return ( now.tv_sec - start_time.tv_sec ) + ( now.tv_nsec * 1e-9 ); + +#ifdef USE_RDTSC_FOR_FLOATTIME + if ( ! s_bTimeInitted ) + { + InitTimeSystem(); + TestTimeSystem(); + } + if ( s_bUseRDTSC ) + { + uint64 nTicks = Plat_Rdtsc() - s_nRDTSCBase; + return ( (double) nTicks) * s_flRDTSCScale; + } + else + { + struct timeval tp; + gettimeofday( &tp, NULL ); + + if (VCRGetMode() == VCR_Disabled) + return (( tp.tv_sec - s_nSecBase ) + tp.tv_usec / 1000000.0 ); + + return VCRHook_Sys_FloatTime( ( tp.tv_sec - s_nSecBase ) + tp.tv_usec / 1000000.0 ); + } +#endif // USE_RDTSC_FOR_FLOATTIME + +#endif +} + +unsigned int Plat_MSTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return (unsigned int)(g_FakeBenchmarkTime * 1000.0); + } + +#ifdef USE_RDTSC_FOR_FLOATTIME + if ( ! s_bTimeInitted ) + { + InitTimeSystem(); + TestTimeSystem(); + } + if ( s_bUseRDTSC ) + { + uint64 nTicks = Plat_Rdtsc() - s_nRDTSCBase; + return 1000.0 * nTicks * s_flRDTSCScale; + } + else +#endif // USE_RDTSC_FOR_FLOATTIME + { + return ( uint )( Plat_FloatTime() * 1000 ); + } +} + +uint64 Plat_USTime() +{ + if ( g_bBenchmarkMode ) + { + g_FakeBenchmarkTime += g_FakeBenchmarkTimeInc; + return (unsigned int)(g_FakeBenchmarkTime * 1000000.0); + } + +#ifdef USE_RDTSC_FOR_FLOATTIME + if ( ! s_bTimeInitted ) + { + InitTimeSystem(); + TestTimeSystem(); + } + if ( s_bUseRDTSC ) + { + uint64 nTicks = Plat_Rdtsc() - s_nRDTSCBase; + return 1000000.0 * nTicks * s_flRDTSCScale; + } + else +#endif // USE_RDTSC_FOR_FLOATTIME + { + return ( uint64 )( Plat_FloatTime() * 1000000 ); + } +} + +// Wraps the thread-safe versions of ctime. buf must be at least 26 bytes +char *Plat_ctime( const time_t *timep, char *buf, size_t bufsize ) +{ + return ctime_r( timep, buf ); +} + +// Wraps the thread-safe versions of gmtime +struct tm *Plat_gmtime( const time_t *timep, struct tm *result ) +{ + return gmtime_r( timep, result ); +} + +time_t Plat_timegm( struct tm *timeptr ) +{ + return timegm( timeptr ); +} + +// Wraps the thread-safe versions of localtime +struct tm *Plat_localtime( const time_t *timep, struct tm *result ) +{ + return localtime_r( timep, result ); +} + +bool vtune( bool resume ) +{ + return 0; +} + + +// -------------------------------------------------------------------------------------------------- // +// Memory stuff. +// -------------------------------------------------------------------------------------------------- // + +#ifndef NO_HOOK_MALLOC + +PLATFORM_INTERFACE void Plat_DefaultAllocErrorFn( unsigned long size ) +{ +} + +typedef void (*Plat_AllocErrorFn)( unsigned long size ); +Plat_AllocErrorFn g_AllocError = Plat_DefaultAllocErrorFn; + +PLATFORM_INTERFACE void* Plat_Alloc( unsigned long size ) +{ + void *pRet = MemAlloc_Alloc( size ); + if ( pRet ) + { + return pRet; + } + else + { + g_AllocError( size ); + return 0; + } +} + + +PLATFORM_INTERFACE void* Plat_Realloc( void *ptr, unsigned long size ) +{ + void *pRet = g_pMemAlloc->Realloc( ptr, size ); + if ( pRet ) + { + return pRet; + } + else + { + g_AllocError( size ); + return 0; + } +} + + +PLATFORM_INTERFACE void Plat_Free( void *ptr ) +{ + g_pMemAlloc->Free( ptr ); +} + + +PLATFORM_INTERFACE void Plat_SetAllocErrorFn( Plat_AllocErrorFn fn ) +{ + g_AllocError = fn; +} + +#endif // !NO_HOOK_MALLOC + +#if defined( OSX ) + +// From the Apple tech note: http://developer.apple.com/library/mac/#qa/qa1361/_index.html +bool Plat_IsInDebugSession() +{ + int junk; + int mib[4]; + struct kinfo_proc info; + size_t size; + static int s_IsInDebugSession = -1; + + if ( s_IsInDebugSession == -1 ) + { + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); + + // We're being debugged if the P_TRACED flag is set. + + s_IsInDebugSession = ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + + return !!s_IsInDebugSession; +} + +#elif defined( LINUX ) + +bool Plat_IsInDebugSession() +{ + // For linux: http://stackoverflow.com/questions/3596781/detect-if-gdb-is-running + // Don't use "if (ptrace(PTRACE_TRACEME, 0, NULL, 0) == -1)" as it means debuggers can't attach. + // Other solutions they mention involve forking. Ugh. + // + // Good solution from Pierre-Loup: Check TracerPid in /proc/self/status. + // from "man proc" + // TracerPid: PID of process tracing this process (0 if not being traced). + int tracerpid = -1; + + int fd = open( "/proc/self/status", O_RDONLY, S_IRUSR ); + if( fd >= 0 ) + { + char buf[ 1024 ]; + static const char s_TracerPid[] = "TracerPid:"; + + int len = read( fd, buf, sizeof( buf ) - 1 ); + if ( len > 0 ) + { + buf[ len ] = 0; + + const char *str = strstr( buf, s_TracerPid ); + tracerpid = str ? atoi( str + sizeof( s_TracerPid ) ) : -1; + } + + close( fd ); + } + + return ( tracerpid > 0 ); +} + + +#endif // defined( LINUX ) + +void Plat_DebugString( const char * psz ) +{ + printf( "%s", psz ); +} + +static char g_CmdLine[ 2048 ]; +PLATFORM_INTERFACE void Plat_SetCommandLine( const char *cmdLine ) +{ + strncpy( g_CmdLine, cmdLine, sizeof(g_CmdLine) ); + g_CmdLine[ sizeof(g_CmdLine) -1 ] = 0; +} + +PLATFORM_INTERFACE const tchar *Plat_GetCommandLine() +{ +#ifdef LINUX + if( !g_CmdLine[ 0 ] ) + { + FILE *fp = fopen( "/proc/self/cmdline", "rb" ); + + if( fp ) + { + size_t nCharRead = 0; + + // -1 to leave room for the '\0' + nCharRead = fread( g_CmdLine, sizeof( g_CmdLine[0] ), ARRAYSIZE( g_CmdLine ) - 1, fp ); + if ( feof( fp ) && !ferror( fp ) ) // Should have read the whole command line without error + { + Assert ( nCharRead < ARRAYSIZE( g_CmdLine ) ); + + for( int i = 0; i < nCharRead; i++ ) + { + if( g_CmdLine[ i ] == '\0' ) + g_CmdLine[ i ] = ' '; + } + + g_CmdLine[ nCharRead ] = '\0'; + + } + fclose( fp ); + } + + Assert( g_CmdLine[ 0 ] ); + } +#endif // LINUX + + return g_CmdLine; +} + +PLATFORM_INTERFACE const char *Plat_GetCommandLineA() +{ + return Plat_GetCommandLine(); +} + +PLATFORM_INTERFACE bool GetMemoryInformation( MemoryInformation *pOutMemoryInfo ) +{ + #if defined( LINUX ) || defined( OSX ) + return false; + #else + #error "Need to fill out GetMemoryInformation or at least return false for this platform" + #endif +} + + +PLATFORM_INTERFACE bool Is64BitOS() +{ +#if defined OSX + return true; +#elif defined LINUX + FILE *pp = popen( "uname -m", "r" ); + if ( pp != NULL ) + { + char rgchArchString[256]; + fgets( rgchArchString, sizeof( rgchArchString ), pp ); + pclose( pp ); + if ( !strncasecmp( rgchArchString, "x86_64", strlen( "x86_64" ) ) ) + return true; + } +#else + Assert( !"implement Is64BitOS" ); +#endif + return false; +} + +PLATFORM_INTERFACE void Plat_ExitProcess( int nCode ) +{ + _exit( nCode ); +} + + +static int s_nWatchDogTimerTimeScale = 0; +static bool s_bInittedWD = false; +static int s_WatchdogTime = 0; +static Plat_WatchDogHandlerFunction_t s_pWatchDogHandlerFunction; + +static void InitWatchDogTimer( void ) +{ + if( !strstr( g_CmdLine, "-nowatchdog" ) ) + { +#ifdef _DEBUG + s_nWatchDogTimerTimeScale = 10; // debug is slow +#else + s_nWatchDogTimerTimeScale = 1; +#endif + + } + +} + +// SIGALRM handler. Used by Watchdog timer code. +static void WatchDogHandler( int s ) +{ + Plat_DebugString( "WatchDog! Server took too long to process (probably infinite loop).\n" ); + + DebuggerBreakIfDebugging(); + + if ( s_pWatchDogHandlerFunction ) + { + s_pWatchDogHandlerFunction(); + } + else + { + // force a crash + abort(); + } +} + +// watchdog timer support +PLATFORM_INTERFACE void Plat_BeginWatchdogTimer( int nSecs ) +{ + if ( !s_bInittedWD ) + { + s_bInittedWD = true; + InitWatchDogTimer(); + } + + nSecs *= s_nWatchDogTimerTimeScale; + nSecs = MIN( nSecs, 5 * 60 ); // no more than 5 minutes no matter what + if ( nSecs ) + { + s_WatchdogTime = nSecs; + signal( SIGALRM, WatchDogHandler ); + alarm( nSecs ); + } +} + +PLATFORM_INTERFACE void Plat_EndWatchdogTimer( void ) +{ + alarm( 0 ); + signal( SIGALRM, SIG_DFL ); + s_WatchdogTime = 0; +} + +PLATFORM_INTERFACE int Plat_GetWatchdogTime( void ) +{ + return s_WatchdogTime; +} + +PLATFORM_INTERFACE void Plat_SetWatchdogHandlerFunction( Plat_WatchDogHandlerFunction_t function ) +{ + s_pWatchDogHandlerFunction = function; +} + +#ifndef NO_HOOK_MALLOC + +// memory logging this functionality is portable code, except for the way in which it hooks +// malloc/free. glibc contains the ability for the app to install hooks into malloc/free. + +#include +#include +#include +#include + +#define MEMALLOC_HASHSIZE 8193 +typedef uint32 ptrint_t; + + + +struct CLinuxMemStats +{ + int nNumMallocs; // total every + int nNumFrees; // total + int nNumMallocsInUse; + int nTotalMallocInUse; + +}; + +#define MAX_STACK_TRACEBACK 20 + +struct CLinuxMallocContext +{ + CLinuxMallocContext *m_pNext; + + void *pStackTraceBack[MAX_STACK_TRACEBACK]; + + int m_nCurrentAllocSize; + int m_nNumAllocsInUse; + int m_nMaximumSize; + int m_TotalNumAllocs; + int m_nLastAllocSize; + + + CLinuxMallocContext( void ) + { + memset( this, 0, sizeof( *this ) ); + } + +}; + + +static CUtlIntrusiveList s_ContextHash[MEMALLOC_HASHSIZE]; + +CLinuxMemStats g_LinuxMemStats; + + +struct RememberedAlloc_t +{ + RememberedAlloc_t *m_pNext, *m_pPrev; // all addresses that hash to the same value are linked + + CLinuxMallocContext *m_pAllocContext; + ptrint_t m_nAddress; // the address of the memory that came from malloc/realloc + size_t m_nSize; + + void AdjustSize( size_t nsize ) + { + int nDelta = nsize - m_nSize; + m_nSize = nsize; + m_pAllocContext->m_nCurrentAllocSize += nDelta; + m_pAllocContext->m_nMaximumSize = MAX( m_pAllocContext->m_nMaximumSize, m_pAllocContext->m_nCurrentAllocSize ); + } + +}; + +static inline int AddressHash( ptrint_t nAdr ) +{ + return ( nAdr % MEMALLOC_HASHSIZE ); +} + +static CUtlIntrusiveDList s_AddressData[MEMALLOC_HASHSIZE]; + +static struct RememberedAlloc_t *FindAddress( void *pAdr, int *pHash = NULL ) +{ + ptrint_t nAdr = ( ptrint_t ) pAdr; + int nHash = AddressHash( nAdr ); + if ( pHash ) + *pHash = nHash; + for( RememberedAlloc_t *i = s_AddressData[nHash].m_pHead; i; i = i->m_pNext ) + { + if ( i->m_nAddress == nAdr ) + return i; + } + return NULL; +} + +#ifdef LINUX +static void *MallocHook( size_t, const void * ); +static void FreeHook( void*, const void * ); +static void *ReallocHook( void *ptr, size_t size, const void *caller ); + +static void RemoveHooks( void ) +{ + __malloc_hook = NULL; + __free_hook = NULL; + __realloc_hook = NULL; +} + + +static void InstallHooks( void ) +{ + __malloc_hook = MallocHook; + __free_hook = FreeHook; + __realloc_hook = ReallocHook; + +} +#elif OSX + + +static void RemoveHooks( void ) +{ +} + + +static void InstallHooks( void ) +{ +} + + +#else +#error +#endif + + +static void AddMemoryAllocation( void *pResult, size_t size ) +{ + if ( pResult ) + { + g_LinuxMemStats.nNumMallocs++; + g_LinuxMemStats.nNumMallocsInUse++; + g_LinuxMemStats.nTotalMallocInUse += size; + + RememberedAlloc_t *pNew = new RememberedAlloc_t; + pNew->m_nAddress = ( ptrint_t ) pResult; + pNew->m_nSize = size; + s_AddressData[AddressHash( pNew->m_nAddress )].AddToHead( pNew ); + + + // now, find the stack traceback context for this call + void *pTraceBack[MAX_STACK_TRACEBACK]; + int nNumGot = backtrace( pTraceBack, ARRAYSIZE( pTraceBack ) ); + for( int n = MAX( 0, nNumGot - 1 ); n < MAX_STACK_TRACEBACK; n++ ) + pTraceBack[n] = NULL; + + uint32 nHash = 0; + for( int i = 0; i < MAX_STACK_TRACEBACK; i++ ) + { + nHash = ( nHash * 3 ) + ( ( ptrint_t ) pTraceBack[i] ); + } + nHash %= MEMALLOC_HASHSIZE; + + CLinuxMallocContext *pFoundCtx = NULL; + // see if we have this context + for( CLinuxMallocContext *i = s_ContextHash[nHash].m_pHead; i ; i = i->m_pNext ) + { + if ( memcmp( pTraceBack, i->pStackTraceBack, sizeof( pTraceBack ) ) == 0 ) + { + pFoundCtx = i; + break; + } + } + if ( ! pFoundCtx ) + { + pFoundCtx = new CLinuxMallocContext; + memcpy( pFoundCtx->pStackTraceBack, pTraceBack, sizeof( pTraceBack ) ); + s_ContextHash[nHash].AddToHead( pFoundCtx ); + } + pNew->m_pAllocContext = pFoundCtx; + pFoundCtx->m_nCurrentAllocSize += size; + pFoundCtx->m_nNumAllocsInUse++; + pFoundCtx->m_nMaximumSize = MAX( pFoundCtx->m_nCurrentAllocSize, pFoundCtx->m_nMaximumSize ); + pFoundCtx->m_TotalNumAllocs++; + } +} + + +static CThreadFastMutex s_MemoryMutex; + +static void *ReallocHook( void *ptr, size_t size, const void *caller ) +{ + AUTO_LOCK( s_MemoryMutex ); + RemoveHooks(); + void *nResult = realloc( ptr, size ); + + if ( ptr ) // did we have this memory before + { + int nHash; + RememberedAlloc_t *pBlock = FindAddress( ptr, &nHash ); + if ( pBlock ) + { + if ( ptr == nResult ) + { + // it successfully alloced, just need to update size info, etc + pBlock->AdjustSize( size ); + g_LinuxMemStats.nTotalMallocInUse += ( size - pBlock->m_nSize ); + + } + else + { + pBlock->m_pAllocContext->m_nCurrentAllocSize -= pBlock->m_nSize; + pBlock->m_pAllocContext->m_nNumAllocsInUse--; + s_AddressData[nHash].RemoveNode( pBlock ); // throw away this node + AddMemoryAllocation( nResult, size ); + } + } + else + AddMemoryAllocation( nResult, size ); + } + else + AddMemoryAllocation( nResult, size ); + InstallHooks(); + return nResult; + + + +} + + +static void *MallocHook(size_t size, const void *caller) +{ + // turn off hooking so we won't recurse + AUTO_LOCK( s_MemoryMutex ); + RemoveHooks(); + + + void *pResult = malloc (size); + + // now, add this memory chunk to our list + AddMemoryAllocation( pResult, size ); + + InstallHooks(); + + return pResult; +} + +static void FreeHook(void *ptr, const void *caller ) +{ + AUTO_LOCK( s_MemoryMutex ); + RemoveHooks(); + + // call real free + free (ptr); + + // look in our list + if ( ptr ) + { + int nHash; + RememberedAlloc_t *pFound = FindAddress( ptr, &nHash ); + if ( !pFound ) + { + //printf(" free of unallocated adr %p (maybe)\n", ptr ); + } + else + { + pFound->m_pAllocContext->m_nCurrentAllocSize -= pFound->m_nSize; + pFound->m_pAllocContext->m_nNumAllocsInUse--; + g_LinuxMemStats.nTotalMallocInUse -= pFound->m_nSize; + g_LinuxMemStats.nNumFrees++; + g_LinuxMemStats.nNumMallocsInUse--; + s_AddressData[nHash].RemoveNode( pFound ); + delete pFound; + } + } + InstallHooks(); +} + +void EnableMemoryLogging( bool bOnOff ) +{ + if ( bOnOff ) + { + InstallHooks(); +#if 0 + // simple test + char *p[10]; + for( int i =0; i < 10; i++ ) + p[i] = new char[10]; + printf( "log with memory\n" ); + DumpMemoryLog(); + for( int i = 0; i < 10; i++ ) + delete[] p[i]; + printf( "after free,\n" ); + DumpMemoryLog(); + + // now, try som realloc action + int *p1 = NULL; + int *p2; + for( int i =1 ; i < 10; i++ ) + { + p1 = (int * ) realloc( p1, i * 100 ); + if ( i == 3 ) + p2 = new int[300]; + } + printf(" after realloc loop\n" ); + DumpMemoryLog(); + delete[] p2; + free( p1 ); + printf(" after realloc frees\n" ); + DumpMemoryLog(); +#endif + } + + else + RemoveHooks(); +} + + +static inline bool SortLessFunc( CLinuxMallocContext * const &left, CLinuxMallocContext * const &right ) +{ + return left->m_nCurrentAllocSize > right->m_nCurrentAllocSize; +} + +void DumpMemoryLog( int nThresh ) +{ + AUTO_LOCK( s_MemoryMutex ); + EndWatchdogTimer(); + RemoveHooks(); + std::vector memList; + + for( int i =0 ; i < MEMALLOC_HASHSIZE; i++ ) + { + for( CLinuxMallocContext *p = s_ContextHash[i].m_pHead; p; p=p->m_pNext ) + { + if ( p->m_nCurrentAllocSize >= nThresh ) + { + memList.push_back( p ); + } + } + } + + std::sort( memList.begin(), memList.end(), SortLessFunc ); + + for( int i = 0; i < memList.size(); i++ ) + { + CLinuxMallocContext *p = memList[i]; + char **strings = backtrace_symbols( p->pStackTraceBack, MAX_STACK_TRACEBACK ); + Msg( "Context cursize=%d nallocs=%d maxsize=%d total_allocs=%d\n", p->m_nCurrentAllocSize, p->m_nNumAllocsInUse, p->m_nMaximumSize, p->m_TotalNumAllocs ); + Msg(" stack\n" ); + for( int n = 0 ; n < MAX_STACK_TRACEBACK; n++ ) + if ( p->pStackTraceBack[n] ) + Msg(" %p %s\n", p->pStackTraceBack[n], strings[n] ); + free( strings ); + } + Msg("End of memory list\n" ); + InstallHooks(); +} + +void DumpChangedMemory( int nThresh ) +{ + AUTO_LOCK( s_MemoryMutex ); + EndWatchdogTimer(); + RemoveHooks(); + std::vector memList; + + for( int i =0 ; i < MEMALLOC_HASHSIZE; i++ ) + { + for( CLinuxMallocContext *p = s_ContextHash[i].m_pHead; p; p=p->m_pNext ) + { + if ( p->m_nCurrentAllocSize - p->m_nLastAllocSize > nThresh ) + { + memList.push_back( p ); + } + } + } + + std::sort( memList.begin(), memList.end(), SortLessFunc ); + for( int i = 0; i < memList.size(); i++ ) + { + CLinuxMallocContext *p = memList[i]; + char **strings = backtrace_symbols( p->pStackTraceBack, MAX_STACK_TRACEBACK ); + Msg( "Context cursize=%d lastsize=%d nallocs=%d maxsize=%d total_allocs=%d\n", p->m_nCurrentAllocSize, p->m_nLastAllocSize, p->m_nNumAllocsInUse, p->m_nMaximumSize, p->m_TotalNumAllocs ); + Msg(" stack\n" ); + for( int n = 0 ; n < MAX_STACK_TRACEBACK; n++ ) + if ( p->pStackTraceBack[n] ) + Msg(" %p %s\n", p->pStackTraceBack[n], strings[n] ); + free( strings ); + } + Msg("End of memory list\n" ); + InstallHooks(); +} + +void SetMemoryMark( void ) +{ + AUTO_LOCK( s_MemoryMutex ); + for( int i =0 ; i < MEMALLOC_HASHSIZE; i++ ) + { + for( CLinuxMallocContext *p = s_ContextHash[i].m_pHead; p; p=p->m_pNext ) + { + p->m_nLastAllocSize = p->m_nCurrentAllocSize; + } + } +} + + +void DumpMemorySummary( void ) +{ + Msg( "Total memory in use = %d, NumMallocs=%d, Num Frees=%d approx process usage=%ul\n", g_LinuxMemStats.nTotalMallocInUse, g_LinuxMemStats.nNumMallocs, g_LinuxMemStats.nNumFrees, + (unsigned int)ApproximateProcessMemoryUsage() ); +} + +#endif // !NO_HOOK_MALLOC + +// Turn off memdbg macros (turned on up top) since this is included like a header +#include "tier0/memdbgoff.h" + + diff --git a/tier0/pmc360.cpp b/tier0/pmc360.cpp new file mode 100644 index 0000000..0d18441 --- /dev/null +++ b/tier0/pmc360.cpp @@ -0,0 +1,88 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Inner workings of Performance Monitor Counters on the xbox 360; +// they let vprof track L2 dcache misses, LHS, etc. +// +//=============================================================================// + +#include "pch_tier0.h" + +#ifndef _X360 +#error pmc360.cpp must only be compiled for XBOX360! +#else + +#include "tier0/platform.h" +#include "tier0/vprof.h" +#include +#include "tier0/dbg.h" +#include "pmc360.h" + +#include "tier0/memdbgon.h" + +static bool s_bInitialized = false; + +CPMCData::CPMCData() +{ +} + +void CPMCData::InitializeOnceProgramWide( void ) +{ +#if !defined( _CERT ) + // Select a set of sixteen counters + DmPMCInstallAndStart( PMC_SETUP_FLUSHREASONS_PB0T0 ); + // Reset the Performance Monitor Counters in preparation for a new sampling run. + DmPMCResetCounters(); +#endif + s_bInitialized = true; +} + +bool CPMCData::IsInitialized() +{ + return s_bInitialized; +} + +void CPMCData::Start() +{ +#if !defined( _CERT ) + // stop the stopwatches, save off the counter, start them again. + DmPMCStop(); + + PMCState pmcstate; + // Get the counters. + DmPMCGetCounters( &pmcstate ); + + // in the default state as set up by InitializeOnceProgramWide, + // counters 9 and 6 are L2 misses and LHS respectively + m_OnStart.L2CacheMiss = pmcstate.pmc[9]; + m_OnStart.LHS = pmcstate.pmc[6]; + + DmPMCStart(); +#endif +} + +void CPMCData::End() +{ +#if !defined( _CERT ) + DmPMCStop(); + + // get end-state counters + PMCState pmcstate; + // Get the counters. + DmPMCGetCounters( &pmcstate ); + + // in the default state as set up by InitializeOnceProgramWide, + // counters 9 and 6 are l2 misses and LHS respectively + const uint64 &endL2 = pmcstate.pmc[9]; + const uint64 &endLHS = pmcstate.pmc[6]; + + // compute delta between end and start. Because these are + // unsigned nums, even in overflow this still works out + // correctly under modular arithmetic. + m_Delta.L2CacheMiss = endL2 - m_OnStart.L2CacheMiss; + m_Delta.LHS = endLHS - m_OnStart.LHS; + + DmPMCStart(); +#endif +} + +#endif diff --git a/tier0/pme.cpp b/tier0/pme.cpp new file mode 100644 index 0000000..512bb68 --- /dev/null +++ b/tier0/pme.cpp @@ -0,0 +1,152 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "pch_tier0.h" +#define WINDOWS_LEAN_AND_MEAN +#include + +#pragma warning( disable : 4530 ) // warning: exception handler -GX option + +#include "tier0/platform.h" +#include "tier0/vprof.h" +#include "tier0/pmelib.h" +#include "tier0/l2cache.h" +#include "tier0/dbg.h" + +//----------------------------------------------------------------------------- +// Purpose: Initialization +//----------------------------------------------------------------------------- +void InitPME( void ) +{ + bool bInit = false; + + PME *pPME = PME::Instance(); + if ( pPME ) + { + if ( pPME->GetVendor() != INTEL ) + return; + + if ( pPME->GetProcessorFamily() != PENTIUM4_FAMILY ) + return; + + pPME->SetProcessPriority( ProcessPriorityHigh ); + + bInit = true; + + DevMsg( 1, _T("PME Initialized.\n") ); + } + else + { + DevMsg( 1, _T("PME Uninitialized.\n") ); + } + + g_VProfCurrentProfile.PMEInitialized( bInit ); +} + +//----------------------------------------------------------------------------- +// Purpose: Shutdown +//----------------------------------------------------------------------------- +void ShutdownPME( void ) +{ + PME *pPME = PME::Instance(); + if ( pPME ) + { + pPME->SetProcessPriority( ProcessPriorityNormal ); + } + + g_VProfCurrentProfile.PMEInitialized( false ); +} + +//============================================================================= +// +// CL2Cache Code. +// + +static int s_nCreateCount = 0; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::CL2Cache() +{ + m_nID = s_nCreateCount++; + m_pL2CacheEvent = new P4Event_BSQ_cache_reference; + m_iL2CacheMissCount = 0; + m_i64Start = 0; + m_i64End = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::~CL2Cache() +{ + if ( m_pL2CacheEvent ) + { + delete m_pL2CacheEvent; + m_pL2CacheEvent = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::Start( void ) +{ + if ( m_pL2CacheEvent ) + { + // Set this up to check for L2 cache misses. + m_pL2CacheEvent->eventMask->RD_2ndL_MISS = 1; + + // Set the event mask and set the capture mode. +// m_pL2CacheEvent->SetCaptureMode( USR_Only ); + m_pL2CacheEvent->SetCaptureMode( OS_and_USR ); + + // That's it, now sw capture events + m_pL2CacheEvent->StopCounter(); + m_pL2CacheEvent->ClearCounter(); + + m_pL2CacheEvent->StartCounter(); + m_i64Start = m_pL2CacheEvent->ReadCounter(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::End( void ) +{ + if ( m_pL2CacheEvent ) + { + // Stop the counter and find the delta. + m_i64End = m_pL2CacheEvent->ReadCounter(); + int64 i64Delta = m_i64End - m_i64Start; + m_pL2CacheEvent->StopCounter(); + + // Save the delta for later query. + m_iL2CacheMissCount = ( int )i64Delta; + } +} + +#pragma warning( default : 4530 ) + +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void CL2Cache::Validate( CValidator &validator, tchar *pchName ) +{ + validator.Push( _T("CL2Cache"), this, pchName ); + + validator.ClaimMemory( m_pL2CacheEvent ); + + validator.Pop( ); +} +#endif // DBGFLAG_VALIDATE + diff --git a/tier0/pme_posix.cpp b/tier0/pme_posix.cpp new file mode 100644 index 0000000..8047191 --- /dev/null +++ b/tier0/pme_posix.cpp @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "tier0/platform.h" +#include "tier0/vprof.h" +#include "tier0/dbg.h" + +//----------------------------------------------------------------------------- +// Purpose: Initialization +//----------------------------------------------------------------------------- +void InitPME( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Shutdown +//----------------------------------------------------------------------------- +void ShutdownPME( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::CL2Cache() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CL2Cache::~CL2Cache() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::Start( void ) +{ +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CL2Cache::End( void ) +{ +} diff --git a/tier0/progressbar.cpp b/tier0/progressbar.cpp new file mode 100644 index 0000000..6da72eb --- /dev/null +++ b/tier0/progressbar.cpp @@ -0,0 +1,37 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" +#include "vstdlib/pch_vstdlib.h" + +#include +#include "tier0/platform.h" +#include "tier0/progressbar.h" + +#if !defined(STEAM) && !defined(NO_MALLOC_OVERRIDE) +#include "tier0/memalloc.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" +#endif + + +static ProgressReportHandler_t pReportHandlerFN; + +PLATFORM_INTERFACE void ReportProgress(char const *job_name, int total_units_to_do, int n_units_completed) +{ + if ( pReportHandlerFN ) + (*pReportHandlerFN)( job_name, total_units_to_do, n_units_completed ); +} + +PLATFORM_INTERFACE ProgressReportHandler_t InstallProgressReportHandler( ProgressReportHandler_t pfn) +{ + ProgressReportHandler_t old = pReportHandlerFN; + pReportHandlerFN = pfn; + return old; +} + diff --git a/tier0/resource.h b/tier0/resource.h new file mode 100644 index 0000000..659b2c3 --- /dev/null +++ b/tier0/resource.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by assert_dialog.rc +// +#define IDD_ASSERT_DIALOG 101 +#define IDC_FILENAME_CONTROL 1000 +#define IDC_LINE_CONTROL 1001 +#define IDC_IGNORE_FILE 1002 +#define IDC_IGNORE_NEARBY 1003 +#define IDC_IGNORE_NUMLINES 1004 +#define IDC_IGNORE_THIS 1005 +#define IDC_BREAK 1006 +#define IDC_IGNORE_ALL 1008 +#define IDC_IGNORE_ALWAYS 1009 +#define IDC_IGNORE_NUMTIMES 1010 +#define IDC_ASSERT_MSG_CTRL 1011 +#define IDC_NOID -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 103 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1005 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/tier0/security.cpp b/tier0/security.cpp new file mode 100644 index 0000000..efcacf1 --- /dev/null +++ b/tier0/security.cpp @@ -0,0 +1,111 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Platform level security functions. +// +//=============================================================================// + +// Uncomment the following line to require the prescence of a hardware key. +//#define REQUIRE_HARDWARE_KEY + +#include "pch_tier0.h" +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN_32_LEAN_AND_MEAN +#include +#endif + +#include "tier0/platform.h" +#include "tier0/vcrmode.h" +#include "tier0/memalloc.h" + +#ifdef REQUIRE_HARDWARE_KEY + // This is the previous key, which was compromised. + //#define VALVE_DESKEY_ID "uW" // Identity Password, Uniquely identifies HL2 keys + #define VALVE_DESKEY_ID "u$" // Identity Password, Uniquely identifies HL2 keys + + // Include the key's API: + #include "DESKey/algo.h" + #include "DESKey/dk2win32.h" + + #pragma comment(lib, "DESKey/algo32.lib" ) + #pragma comment(lib, "DESKey/dk2win32.lib" ) +#endif + +bool Plat_VerifyHardwareKey() +{ +#ifdef REQUIRE_HARDWARE_KEY + + // Ensure that a key with our ID exists: + if ( FindDK2( VALVE_DESKEY_ID, NULL ) ) + return true; + + return false; +#else + return true; +#endif +} + +bool Plat_VerifyHardwareKeyDriver() +{ +#ifdef REQUIRE_HARDWARE_KEY + // Ensure that the driver is at least installed: + return DK2DriverInstalled() != 0; +#else + return true; +#endif +} + +bool Plat_VerifyHardwareKeyPrompt() +{ +#ifdef REQUIRE_HARDWARE_KEY + if ( !DK2DriverInstalled() ) + { + if( IDCANCEL == MessageBox( NULL, "No drivers detected for the hardware key, please install them and re-run the application.\n", "No Driver Detected", MB_OKCANCEL ) ) + { + return false; + } + } + + while ( !Plat_VerifyHardwareKey() ) + { + if ( IDCANCEL == MessageBox( NULL, "Please insert the hardware key and hit 'ok'.\n", "Insert Hardware Key", MB_OKCANCEL ) ) + { + return false; + } + + for ( int i=0; i < 2; i++ ) + { + // Is the key in now? + if( Plat_VerifyHardwareKey() ) + { + return true; + } + + // Sleep 2 / 3 of a second before trying again, in case the os recognizes the key slightly after it's being inserted: + Sleep(666); + } + } + + return true; +#else + return true; +#endif +} + +bool Plat_FastVerifyHardwareKey() +{ +#ifdef REQUIRE_HARDWARE_KEY + static int nIterations = 0; + + nIterations++; + if( nIterations > 100 ) + { + nIterations = 0; + return Plat_VerifyHardwareKey(); + } + + return true; +#else + return true; +#endif +} + diff --git a/tier0/security_linux.cpp b/tier0/security_linux.cpp new file mode 100644 index 0000000..0bc9840 --- /dev/null +++ b/tier0/security_linux.cpp @@ -0,0 +1,125 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Platform level security functions. +// +// $NoKeywords: $ +//=============================================================================// + +// Uncomment the following line to require the prescence of a hardware key. +//#define REQUIRE_HARDWARE_KEY +// NOTE - this DOESN'T work under linux!!!! + + +#include "tier0/platform.h" +#include "tier0/vcrmode.h" +#include "tier0/memalloc.h" + + +#ifdef REQUIRE_HARDWARE_KEY + + #define VALVE_DESKEY_ID "uW" // Uniquely identifies HL2 keys + + // Include the key's API: + #include "DESKey/algo.h" + #include "DESKey/dk2win32.h" + +// #pragma comment(lib, "DESKey/algo32.lib" ) +// #pragma comment(lib, "DESKey/dk2win32.lib" ) + +#endif + +bool Plat_VerifyHardwareKey() +{ + #ifdef REQUIRE_HARDWARE_KEY + + // Ensure that a key with our ID exists: + if( FindDK2( VALVE_DESKEY_ID, NULL ) ) + return true; + + return false; + + #else + + return true; + + #endif +} + +bool Plat_VerifyHardwareKeyDriver() +{ + #ifdef REQUIRE_HARDWARE_KEY + + // Ensure that the driver is at least installed: + return DK2DriverInstalled() != 0; + + #else + + return true; + + #endif +} + +bool Plat_VerifyHardwareKeyPrompt() +{ + #ifdef REQUIRE_HARDWARE_KEY + + if( !DK2DriverInstalled() ) + { + if( IDCANCEL == MessageBox( NULL, "No drivers detected for the hardware key, please install them and re-run the application.\n", "No Driver Detected", MB_OKCANCEL ) ) + { + return false; + } + + } + + while( !Plat_VerifyHardwareKey() ) + { + if( IDCANCEL == MessageBox( NULL, "Please insert the hardware key and hit 'ok'.\n", "Insert Hardware Key", MB_OKCANCEL ) ) + { + return false; + } + + for( int i=0; i < 2; i++ ) + { + // Is the key in now? + if( Plat_VerifyHardwareKey() ) + { + return true; + } + + // Sleep 2 / 3 of a second before trying again, in case the os recognizes the key slightly after it's being inserted: + Sleep(666); + } + } + + return true; + + #else + + return true; + + #endif +} + +bool Plat_FastVerifyHardwareKey() +{ + #ifdef REQUIRE_HARDWARE_KEY + + static int nIterations = 0; + + nIterations++; + if( nIterations > 100 ) + { + nIterations = 0; + return Plat_VerifyHardwareKey(); + } + + return true; + + #else + + return true; + + #endif +} + diff --git a/tier0/stacktools.cpp b/tier0/stacktools.cpp new file mode 100644 index 0000000..7b0da4f --- /dev/null +++ b/tier0/stacktools.cpp @@ -0,0 +1,1707 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// + +#include "pch_tier0.h" +#include "tier0/stacktools.h" +#include "tier0/threadtools.h" +#include "tier0/icommandline.h" + +#include "tier0/valve_off.h" + +#if defined( PLATFORM_WINDOWS_PC ) +#define WIN32_LEAN_AND_MEAN +#include +#include +#endif + +#if defined( PLATFORM_X360 ) +#include +#include "xbox/xbox_console.h" +#include "xbox/xbox_vxconsole.h" +#include +#include +#endif + +#if defined( LINUX ) +#include +#endif + +#include "tier0/valve_on.h" + +#include "tier0/memdbgon.h" + + +#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) //disable the whole toolset + +#if defined( LINUX ) + +int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + return backtrace( pReturnAddressesOut, iArrayCount ); +} + +int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + return backtrace( pReturnAddressesOut, iArrayCount ); +} + +#else + +int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + return 0; +} + +int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + return 0; +} + +#endif + +//where we'll find our PDB's for win32. Translation will not work until this has been called once (even if with NULL) +void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) +{ +} + +void StackToolsNotify_LoadedLibrary( const char *szLibName ) +{ +} + +int TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style ) +{ + if( iOutBufferSize > 0 ) + *szOutput = '\0'; + + return 0; +} + +void PreloadStackInformation( const void **pAddresses, int iAddressCount ) +{ +} + +bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) +{ + if( iMaxFileNameLength > 0 ) + *pFileNameOut = '\0'; + + return false; +} + +bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) +{ + if( iMaxSymbolNameLength > 0 ) + *pSymbolNameOut = '\0'; + + return false; +} + +bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) +{ + if( iMaxModuleNameLength > 0 ) + *pModuleNameOut = '\0'; + + return false; +} + +#else //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + +//=============================================================================================================== +// Shared Windows/X360 code +//=============================================================================================================== + +CTHREADLOCALPTR( CStackTop_Base ) g_StackTop; + +class CStackTop_FriendFuncs : public CStackTop_Base +{ +public: + friend int AppendParentStackTrace( void **pReturnAddressesOut, int iArrayCount, int iAlreadyFilled ); + friend int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ); +}; + + +inline int AppendParentStackTrace( void **pReturnAddressesOut, int iArrayCount, int iAlreadyFilled ) +{ + CStackTop_FriendFuncs *pTop = (CStackTop_FriendFuncs *)(CStackTop_Base *)g_StackTop; + if( pTop != NULL ) + { + if( pTop->m_pReplaceAddress != NULL ) + { + for( int i = iAlreadyFilled; --i >= 0; ) + { + if( pReturnAddressesOut[i] == pTop->m_pReplaceAddress ) + { + iAlreadyFilled = i; + break; + } + } + } + + if( pTop->m_iParentStackTraceLength != 0 ) + { + int iCopy = MIN( iArrayCount - iAlreadyFilled, pTop->m_iParentStackTraceLength ); + memcpy( pReturnAddressesOut + iAlreadyFilled, pTop->m_pParentStackTrace, iCopy * sizeof( void * ) ); + iAlreadyFilled += iCopy; + } + } + + return iAlreadyFilled; +} + +inline bool ValidStackAddress( void *pAddress, const void *pNoLessThan, const void *pNoGreaterThan ) +{ + if( (uintp)pAddress & 3 ) + return false; + if( pAddress < pNoLessThan ) //frame pointer traversal should always increase the pointer + return false; + if( pAddress > pNoGreaterThan ) //never traverse outside the stack (Oh 0xCCCCCCCC, how I hate you) + return false; + +#if defined( WIN32 ) && !defined( _X360 ) && 1 + if( IsBadReadPtr( pAddress, (sizeof( void * ) * 2) ) ) //safety net, but also throws an exception (handled internally) to stop bad access + return false; +#endif + + return true; +} + +#pragma auto_inline( off ) +int GetCallStack_Fast( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + //Only tested in windows. This function won't work with frame pointer omission enabled. "vpc /nofpo" all projects +#if (defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) &&\ + (defined( WIN32 ) && !defined( _X360 ) && !defined(_M_X64)) + void *pStackCrawlEBP; + __asm + { + mov [pStackCrawlEBP], ebp; + } + + /* + With frame pointer omission disabled, this should be the pattern all the way up the stack + [ebp+00] Old ebp value + [ebp+04] Return address + */ + + void *pNoLessThan = pStackCrawlEBP; //impossible for a valid stack to traverse before this address + int i; + + CStackTop_FriendFuncs *pTop = (CStackTop_FriendFuncs *)(CStackTop_Base *)g_StackTop; + if( pTop != NULL ) //we can do fewer error checks if we have a valid reference point for the top of the stack + { + void *pNoGreaterThan = pTop->m_pStackBase; + + //skips + for( i = 0; i != iSkipCount; ++i ) + { + if( (pStackCrawlEBP < pNoLessThan) || (pStackCrawlEBP > pNoGreaterThan) ) + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, 0 ); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value + } + + //store + for( i = 0; i != iArrayCount; ++i ) + { + if( (pStackCrawlEBP < pNoLessThan) || (pStackCrawlEBP > pNoGreaterThan) ) + break; + + pReturnAddressesOut[i] = *((void **)pStackCrawlEBP + 1); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value + } + + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, i ); + } + else + { + void *pNoGreaterThan = ((unsigned char *)pNoLessThan) + (1024 * 1024); //standard stack is 1MB. TODO: Get actual stack end address if available since this check isn't foolproof + + //skips + for( i = 0; i != iSkipCount; ++i ) + { + if( !ValidStackAddress( pStackCrawlEBP, pNoLessThan, pNoGreaterThan ) ) + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, 0 ); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value + } + + //store + for( i = 0; i != iArrayCount; ++i ) + { + if( !ValidStackAddress( pStackCrawlEBP, pNoLessThan, pNoGreaterThan ) ) + break; + + pReturnAddressesOut[i] = *((void **)pStackCrawlEBP + 1); + + pNoLessThan = pStackCrawlEBP; + pStackCrawlEBP = *(void **)pStackCrawlEBP; //should be pointing at old ebp value + } + + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, i ); + } +#endif + + return 0; +} +#pragma auto_inline( on ) + + + + + + +#if defined( WIN32 ) && !defined( _X360 ) +//=============================================================================================================== +// Windows version of the toolset +//=============================================================================================================== + + +#if defined( TIER0_FPO_DISABLED ) +//# define USE_CAPTURESTACKBACKTRACE //faster than StackWalk64, but only works on XP or newer and only with Frame Pointer Omission optimization disabled(/Oy-) for every function it traces through +#endif + + +#if defined(_M_IX86) || defined(_M_X64) +# define USE_STACKWALK64 +# if defined(_M_IX86) +# define STACKWALK64_MACHINETYPE IMAGE_FILE_MACHINE_I386 +# else +# define STACKWALK64_MACHINETYPE IMAGE_FILE_MACHINE_AMD64 +# endif +#endif + +typedef DWORD (WINAPI *PFN_SymGetOptions)( VOID ); +typedef DWORD (WINAPI *PFN_SymSetOptions)( IN DWORD SymOptions ); +typedef BOOL (WINAPI *PFN_SymSetSearchPath)( IN HANDLE hProcess, IN PSTR SearchPath ); +typedef BOOL (WINAPI *PFN_SymInitialize)( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ); +typedef BOOL (WINAPI *PFN_SymCleanup)( IN HANDLE hProcess ); +typedef BOOL (WINAPI *PFN_SymEnumerateModules64)( IN HANDLE hProcess, IN PSYM_ENUMMODULES_CALLBACK64 EnumModulesCallback, IN PVOID UserContext ); +typedef BOOL (WINAPI *PFN_EnumerateLoadedModules64)( IN HANDLE hProcess, IN PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, IN PVOID UserContext ); +typedef DWORD64 (WINAPI *PFN_SymLoadModule64)( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); +typedef BOOL (WINAPI *PFN_SymUnloadModule64)( IN HANDLE hProcess, IN DWORD64 BaseOfDll ); +typedef BOOL (WINAPI *PFN_SymFromAddr)( IN HANDLE hProcess, IN DWORD64 Address, OUT PDWORD64 Displacement, IN OUT PSYMBOL_INFO Symbol ); +typedef BOOL (WINAPI *PFN_SymGetLineFromAddr64)( IN HANDLE hProcess, IN DWORD64 qwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line64 ); +typedef BOOL (WINAPI *PFN_SymGetModuleInfo64)( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PIMAGEHLP_MODULE64 ModuleInfo ); +typedef BOOL (WINAPI *PFN_StackWalk64)( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ); +typedef USHORT (WINAPI *PFN_CaptureStackBackTrace)( IN ULONG FramesToSkip, IN ULONG FramesToCapture, OUT PVOID *BackTrace, OUT OPTIONAL PULONG BackTraceHash ); + + +DWORD WINAPI SymGetOptions_DummyFn( VOID ) +{ + return 0; +} + +DWORD WINAPI SymSetOptions_DummyFn( IN DWORD SymOptions ) +{ + return 0; +} + +BOOL WINAPI SymSetSearchPath_DummyFn( IN HANDLE hProcess, IN PSTR SearchPath ) +{ + return FALSE; +} + +BOOL WINAPI SymInitialize_DummyFn( IN HANDLE hProcess, IN PSTR UserSearchPath, IN BOOL fInvadeProcess ) +{ + return FALSE; +} + +BOOL WINAPI SymCleanup_DummyFn( IN HANDLE hProcess ) +{ + return TRUE; +} + +BOOL WINAPI SymEnumerateModules64_DummyFn( IN HANDLE hProcess, IN PSYM_ENUMMODULES_CALLBACK64 EnumModulesCallback, IN PVOID UserContext ) +{ + return FALSE; +} + +BOOL WINAPI EnumerateLoadedModules64_DummyFn( IN HANDLE hProcess, IN PENUMLOADED_MODULES_CALLBACK64 EnumLoadedModulesCallback, IN PVOID UserContext ) +{ + return FALSE; +} + +DWORD64 WINAPI SymLoadModule64_DummyFn( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ) +{ + return 0; +} + +BOOL WINAPI SymUnloadModule64_DummyFn( IN HANDLE hProcess, IN DWORD64 BaseOfDll ) +{ + return FALSE; +} + +BOOL WINAPI SymFromAddr_DummyFn( IN HANDLE hProcess, IN DWORD64 Address, OUT PDWORD64 Displacement, IN OUT PSYMBOL_INFO Symbol ) +{ + return FALSE; +} + +BOOL WINAPI SymGetLineFromAddr64_DummyFn( IN HANDLE hProcess, IN DWORD64 qwAddr, OUT PDWORD pdwDisplacement, OUT PIMAGEHLP_LINE64 Line64 ) +{ + return FALSE; +} + +BOOL WINAPI SymGetModuleInfo64_DummyFn( IN HANDLE hProcess, IN DWORD64 dwAddr, OUT PIMAGEHLP_MODULE64 ModuleInfo ) +{ + return FALSE; +} + + + +BOOL WINAPI StackWalk64_DummyFn( DWORD MachineType, HANDLE hProcess, HANDLE hThread, LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress ) +{ + return FALSE; +} + +USHORT WINAPI CaptureStackBackTrace_DummyFn( IN ULONG FramesToSkip, IN ULONG FramesToCapture, OUT PVOID *BackTrace, OUT OPTIONAL PULONG BackTraceHash ) +{ + return 0; +} + +class CHelperFunctionsLoader +{ +public: + CHelperFunctionsLoader( void ) + { + m_bIsInitialized = false; + m_bShouldReloadSymbols = false; + m_hDbgHelpDll = NULL; + m_szPDBSearchPath = NULL; + m_pSymInitialize = SymInitialize_DummyFn; + m_pSymCleanup = SymCleanup_DummyFn; + m_pSymSetOptions = SymSetOptions_DummyFn; + m_pSymGetOptions = SymGetOptions_DummyFn; + m_pSymSetSearchPath = SymSetSearchPath_DummyFn; + m_pSymEnumerateModules64 = SymEnumerateModules64_DummyFn; + m_pEnumerateLoadedModules64 = EnumerateLoadedModules64_DummyFn; + m_pSymLoadModule64 = SymLoadModule64_DummyFn; + m_pSymUnloadModule64 = SymUnloadModule64_DummyFn; + m_pSymFromAddr = SymFromAddr_DummyFn; + m_pSymGetLineFromAddr64 = SymGetLineFromAddr64_DummyFn; + m_pSymGetModuleInfo64 = SymGetModuleInfo64_DummyFn; + +#if defined( USE_STACKWALK64 ) + m_pStackWalk64 = StackWalk64_DummyFn; +#endif + +#if defined( USE_CAPTURESTACKBACKTRACE ) + m_pCaptureStackBackTrace = CaptureStackBackTrace_DummyFn; + m_hNTDllDll = NULL; +#endif + } + + ~CHelperFunctionsLoader( void ) + { + m_pSymCleanup( m_hProcess ); + + if( m_hDbgHelpDll != NULL ) + ::FreeLibrary( m_hDbgHelpDll ); + +#if defined( USE_CAPTURESTACKBACKTRACE ) + if( m_hNTDllDll != NULL ) + ::FreeLibrary( m_hNTDllDll ); +#endif + + if( m_szPDBSearchPath != NULL ) + delete []m_szPDBSearchPath; + } + + static BOOL CALLBACK UnloadSymbolsCallback( PSTR ModuleName, DWORD64 BaseOfDll, PVOID UserContext ) + { + const CHelperFunctionsLoader *pThis = ((CHelperFunctionsLoader *)UserContext); + pThis->m_pSymUnloadModule64( pThis->m_hProcess, BaseOfDll ); + return TRUE; + } + +#if _MSC_VER >= 1600 + static BOOL CALLBACK LoadSymbolsCallback( PCSTR ModuleName, DWORD64 ModuleBase, ULONG ModuleSize, PVOID UserContext ) +#else + static BOOL CALLBACK LoadSymbolsCallback( PSTR ModuleName, DWORD64 ModuleBase, ULONG ModuleSize, PVOID UserContext ) +#endif + { + const CHelperFunctionsLoader *pThis = ((CHelperFunctionsLoader *)UserContext); + //SymLoadModule64( IN HANDLE hProcess, IN HANDLE hFile, IN PSTR ImageName, IN PSTR ModuleName, IN DWORD64 BaseOfDll, IN DWORD SizeOfDll ); + pThis->m_pSymLoadModule64( pThis->m_hProcess, NULL, (PSTR)ModuleName, (PSTR)ModuleName, ModuleBase, ModuleSize ); + return TRUE; + } + + void TryLoadingNewSymbols( void ) + { + AUTO_LOCK( m_Mutex ); + + if( m_bIsInitialized ) + { + //m_pSymEnumerateModules64( m_hProcess, UnloadSymbolsCallback, this ); //unloaded modules we've already loaded + m_pEnumerateLoadedModules64( m_hProcess, LoadSymbolsCallback, this ); //load everything + m_bShouldReloadSymbols = false; + } + } + + void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) + { + AUTO_LOCK( m_Mutex ); + + if( m_szPDBSearchPath != NULL ) + delete []m_szPDBSearchPath; + + if( szSemicolonSeparatedList == NULL ) + { + m_szPDBSearchPath = NULL; + return; + } + + int iLength = (int)strlen( szSemicolonSeparatedList ) + 1; + char *pNewPath = new char [iLength]; + memcpy( pNewPath, szSemicolonSeparatedList, iLength ); + m_szPDBSearchPath = pNewPath; + + //re-init search paths. Or if we haven't yet loaded dbghelp.dll, this will go to the dummy function and do nothing + m_pSymSetSearchPath( m_hProcess, m_szPDBSearchPath ); + //TryLoadingNewSymbols(); + m_bShouldReloadSymbols = true; + } + + bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) + { + if( pAddress == NULL ) + return false; + + AUTO_LOCK( m_Mutex ); + + unsigned char genericbuffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME*sizeof(TCHAR)]; + + ((PSYMBOL_INFO)genericbuffer)->SizeOfStruct = sizeof(SYMBOL_INFO); + ((PSYMBOL_INFO)genericbuffer)->MaxNameLen = MAX_SYM_NAME; + + DWORD64 dwDisplacement; + if( m_pSymFromAddr( m_hProcess, (DWORD64)pAddress, &dwDisplacement, (PSYMBOL_INFO)genericbuffer) ) + { + strncpy( pSymbolNameOut, ((PSYMBOL_INFO)genericbuffer)->Name, iMaxSymbolNameLength ); + if( pDisplacementOut != NULL ) + *pDisplacementOut = dwDisplacement; + return true; + } + + return false; + } + + bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) + { + if( pAddress == NULL ) + return false; + + AUTO_LOCK( m_Mutex ); + + tchar szBuffer[1024]; + szBuffer[0] = _T('\0'); + + IMAGEHLP_LINE64 imageHelpLine64; + imageHelpLine64.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + imageHelpLine64.FileName = szBuffer; + + DWORD dwDisplacement; + if( m_pSymGetLineFromAddr64( m_hProcess, (DWORD64)pAddress, &dwDisplacement, &imageHelpLine64 ) ) + { + strncpy( pFileNameOut, imageHelpLine64.FileName, iMaxFileNameLength ); + iLineNumberOut = imageHelpLine64.LineNumber; + + if( pDisplacementOut != NULL ) + *pDisplacementOut = dwDisplacement; + + return true; + } + + return false; + } + + bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) + { + AUTO_LOCK( m_Mutex ); + IMAGEHLP_MODULE64 moduleInfo; + + moduleInfo.SizeOfStruct = sizeof(IMAGEHLP_MODULE64); + + if ( m_pSymGetModuleInfo64( m_hProcess, (DWORD64)pAddress, &moduleInfo ) ) + { + strncpy( pModuleNameOut, moduleInfo.ModuleName, iMaxModuleNameLength ); + return true; + } + + return false; + } + + //only returns false if we ran out of buffer space. + bool TranslatePointer( const void * const pAddress, tchar *pTranslationOut, int iTranslationBufferLength, TranslateStackInfo_StyleFlags_t style ) + { + //AUTO_LOCK( m_Mutex ); + + if( pTranslationOut == NULL ) + return false; + + if( iTranslationBufferLength <= 0 ) + return false; + + //sample desired output + // valid translation - "tier0.dll!CHelperFunctionsLoader::TranslatePointer - u:\Dev\L4D\src\tier0\stacktools.cpp(162) + 4 bytes" + // fallback translation - "tier0.dll!0x01234567" + + tchar *pWrite = pTranslationOut; + *pWrite = '\0'; + int iLength; + + if( style & TSISTYLEFLAG_MODULENAME ) + { + if( !this->GetModuleNameFromAddress( pAddress, pWrite, iTranslationBufferLength ) ) + strncpy( pWrite, "unknown_module", iTranslationBufferLength ); + + iLength = (int)strlen( pWrite ); + pWrite += iLength; + iTranslationBufferLength -= iLength; + + if( iTranslationBufferLength < 2 ) + return false; //need more buffer + + if( style & TSISTYLEFLAG_SYMBOLNAME ) + { + *pWrite = '!'; + ++pWrite; + --iTranslationBufferLength; + *pWrite = '\0'; + } + } + + //use symbol name to test if the rest is going to work. So grab it whether they want it or not + if( !this->GetSymbolNameFromAddress( pAddress, pWrite, iTranslationBufferLength, NULL ) ) + { + int nBytesWritten = _snprintf( pWrite, iTranslationBufferLength, "0x%p", pAddress ); + if ( nBytesWritten < 0 ) + { + *pWrite = '\0'; // if we can't write all of the line/lineandoffset, don't write any at all + return false; + } + return true; + } + else if( style & TSISTYLEFLAG_SYMBOLNAME ) + { + iLength = (int)strlen( pWrite ); + pWrite += iLength; + iTranslationBufferLength -= iLength; + } + else + { + *pWrite = '\0'; //symbol name lookup worked, but unwanted, discard + } + + if( style & (TSISTYLEFLAG_FULLPATH | TSISTYLEFLAG_SHORTPATH | TSISTYLEFLAG_LINE | TSISTYLEFLAG_LINEANDOFFSET) ) + { + if( pWrite != pTranslationOut ) //if we've written anything yet, separate the printed data from the file name and line + { + if( iTranslationBufferLength < 6 ) + return false; //need more buffer + + pWrite[0] = ' '; //append " - " + pWrite[1] = '-'; + pWrite[2] = ' '; + pWrite[3] = '\0'; + pWrite += 3; + iTranslationBufferLength -= 3; + } + + uint32 iLine; + uint32 iDisplacement; + char szFileName[MAX_PATH]; + if( this->GetFileAndLineFromAddress( pAddress, szFileName, MAX_PATH, iLine, &iDisplacement ) ) + { + if( style & TSISTYLEFLAG_FULLPATH ) + { + iLength = (int)strlen( szFileName ); + if ( iTranslationBufferLength < iLength + 1 ) + return false; + + memcpy( pWrite, szFileName, iLength + 1 ); + pWrite += iLength; + iTranslationBufferLength -= iLength; + } + else if( style & TSISTYLEFLAG_SHORTPATH ) + { + //shorten the path and copy + iLength = (int)strlen( szFileName ); + char *pShortened = szFileName + iLength; + int iSlashesAllowed = 3; + while( pShortened > szFileName ) + { + if( (*pShortened == '\\') || (*pShortened == '/') ) + { + --iSlashesAllowed; + if( iSlashesAllowed == 0 ) + break; + } + + --pShortened; + } + + iLength = (int)strlen( pShortened ); + if( iTranslationBufferLength < iLength + 1 ) + { + //Remove the " - " that we can't append to + pWrite -= 3; + iTranslationBufferLength += 3; + *pWrite = '\0'; + return false; + } + + memcpy( pWrite, szFileName, iLength + 1 ); + pWrite += iLength; + iTranslationBufferLength -= iLength; + } + + if( style & (TSISTYLEFLAG_LINE | TSISTYLEFLAG_LINEANDOFFSET) ) + { + int nBytesWritten = _snprintf( pWrite, iTranslationBufferLength, ((style & TSISTYLEFLAG_LINEANDOFFSET) && (iDisplacement != 0)) ? "(%d) + %d bytes" : "(%d)", iLine, iDisplacement ); + if ( nBytesWritten < 0 ) + { + *pWrite = '\0'; // if we can't write all of the line/lineandoffset, don't write any at all + return false; + } + + pWrite += nBytesWritten; + iTranslationBufferLength -= nBytesWritten; + } + } + else + { + //Remove the " - " that we didn't append to + pWrite -= 3; + iTranslationBufferLength += 3; + *pWrite = '\0'; + } + } + + return true; + } + + + //about to actually use the functions, load if necessary + void EnsureReady( void ) + { + if( m_bIsInitialized ) + { + if( m_bShouldReloadSymbols ) + TryLoadingNewSymbols(); + + return; + } + + AUTO_LOCK( m_Mutex ); + + //Only enabled for P4 and Steam Beta builds + if( (CommandLine()->FindParm( "-steam" ) != 0) && //is steam + (CommandLine()->FindParm( "-internalbuild" ) == 0) ) //is not steam beta + { + //disable the toolset by falsifying initialized state + m_bIsInitialized = true; + return; + } + + m_hProcess = GetCurrentProcess(); + if( m_hProcess == NULL ) + return; + + m_bIsInitialized = true; + + // get the function pointer directly so that we don't have to include the .lib, and that + // we can easily change it to using our own dll when this code is used on win98/ME/2K machines + m_hDbgHelpDll = ::LoadLibrary( "DbgHelp.dll" ); + if ( !m_hDbgHelpDll ) + { + //it's possible it's just way too early to initialize (as shown with attempts at using these tools in the memory allocator) + if( m_szPDBSearchPath == NULL ) //not a rock solid check, but pretty good compromise between endless failing initialization and general failure due to trying too early + m_bIsInitialized = false; + + return; + } + + m_pSymInitialize = (PFN_SymInitialize) ::GetProcAddress( m_hDbgHelpDll, "SymInitialize" ); + if( m_pSymInitialize == NULL ) + { + //very bad + ::FreeLibrary( m_hDbgHelpDll ); + m_hDbgHelpDll = NULL; + m_pSymInitialize = SymInitialize_DummyFn; + return; + } + + m_pSymCleanup = (PFN_SymCleanup) ::GetProcAddress( m_hDbgHelpDll, "SymCleanup" ); + if( m_pSymCleanup == NULL ) + m_pSymCleanup = SymCleanup_DummyFn; + + m_pSymGetOptions = (PFN_SymGetOptions) ::GetProcAddress( m_hDbgHelpDll, "SymGetOptions" ); + if( m_pSymGetOptions == NULL ) + m_pSymGetOptions = SymGetOptions_DummyFn; + + m_pSymSetOptions = (PFN_SymSetOptions) ::GetProcAddress( m_hDbgHelpDll, "SymSetOptions" ); + if( m_pSymSetOptions == NULL ) + m_pSymSetOptions = SymSetOptions_DummyFn; + + m_pSymSetSearchPath = (PFN_SymSetSearchPath) ::GetProcAddress( m_hDbgHelpDll, "SymSetSearchPath" ); + if( m_pSymSetSearchPath == NULL ) + m_pSymSetSearchPath = SymSetSearchPath_DummyFn; + + m_pSymEnumerateModules64 = (PFN_SymEnumerateModules64) ::GetProcAddress( m_hDbgHelpDll, "SymEnumerateModules64" ); + if( m_pSymEnumerateModules64 == NULL ) + m_pSymEnumerateModules64 = SymEnumerateModules64_DummyFn; + + m_pEnumerateLoadedModules64 = (PFN_EnumerateLoadedModules64) ::GetProcAddress( m_hDbgHelpDll, "EnumerateLoadedModules64" ); + if( m_pEnumerateLoadedModules64 == NULL ) + m_pEnumerateLoadedModules64 = EnumerateLoadedModules64_DummyFn; + + m_pSymLoadModule64 = (PFN_SymLoadModule64) ::GetProcAddress( m_hDbgHelpDll, "SymLoadModule64" ); + if( m_pSymLoadModule64 == NULL ) + m_pSymLoadModule64 = SymLoadModule64_DummyFn; + + m_pSymUnloadModule64 = (PFN_SymUnloadModule64) ::GetProcAddress( m_hDbgHelpDll, "SymUnloadModule64" ); + if( m_pSymUnloadModule64 == NULL ) + m_pSymUnloadModule64 = SymUnloadModule64_DummyFn; + + m_pSymFromAddr = (PFN_SymFromAddr) ::GetProcAddress( m_hDbgHelpDll, "SymFromAddr" ); + if( m_pSymFromAddr == NULL ) + m_pSymFromAddr = SymFromAddr_DummyFn; + + m_pSymGetLineFromAddr64 = (PFN_SymGetLineFromAddr64) ::GetProcAddress( m_hDbgHelpDll, "SymGetLineFromAddr64" ); + if( m_pSymGetLineFromAddr64 == NULL ) + m_pSymGetLineFromAddr64 = SymGetLineFromAddr64_DummyFn; + + m_pSymGetModuleInfo64 = (PFN_SymGetModuleInfo64) ::GetProcAddress( m_hDbgHelpDll, "SymGetModuleInfo64" ); + if( m_pSymGetModuleInfo64 == NULL ) + m_pSymGetModuleInfo64 = SymGetModuleInfo64_DummyFn; + +#if defined( USE_STACKWALK64 ) + m_pStackWalk64 = (PFN_StackWalk64) ::GetProcAddress( m_hDbgHelpDll, "StackWalk64" ); + if( m_pStackWalk64 == NULL ) + m_pStackWalk64 = StackWalk64_DummyFn; +#endif + + +#if defined( USE_CAPTURESTACKBACKTRACE ) + m_hNTDllDll = ::LoadLibrary( "ntdll.dll" ); + + m_pCaptureStackBackTrace = (PFN_CaptureStackBackTrace) ::GetProcAddress( m_hNTDllDll, "RtlCaptureStackBackTrace" ); + if( m_pCaptureStackBackTrace == NULL ) + m_pCaptureStackBackTrace = CaptureStackBackTrace_DummyFn; +#endif + + + m_pSymSetOptions( m_pSymGetOptions() | + SYMOPT_DEFERRED_LOADS | //load on demand + SYMOPT_EXACT_SYMBOLS | //don't load the wrong file + SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_NO_PROMPTS | //don't prompt ever + SYMOPT_LOAD_LINES ); //load line info + + m_pSymInitialize( m_hProcess, m_szPDBSearchPath, FALSE ); + TryLoadingNewSymbols(); + } + + bool m_bIsInitialized; + bool m_bShouldReloadSymbols; + HANDLE m_hProcess; + HMODULE m_hDbgHelpDll; + char *m_szPDBSearchPath; + CThreadFastMutex m_Mutex; //DbgHelp functions are all single threaded. + + PFN_SymInitialize m_pSymInitialize; + PFN_SymCleanup m_pSymCleanup; + PFN_SymGetOptions m_pSymGetOptions; + PFN_SymSetOptions m_pSymSetOptions; + PFN_SymSetSearchPath m_pSymSetSearchPath; + PFN_SymEnumerateModules64 m_pSymEnumerateModules64; + PFN_EnumerateLoadedModules64 m_pEnumerateLoadedModules64; + PFN_SymLoadModule64 m_pSymLoadModule64; + PFN_SymUnloadModule64 m_pSymUnloadModule64; + + PFN_SymFromAddr m_pSymFromAddr; + PFN_SymGetLineFromAddr64 m_pSymGetLineFromAddr64; + PFN_SymGetModuleInfo64 m_pSymGetModuleInfo64; + +#if defined( USE_STACKWALK64 ) + PFN_StackWalk64 m_pStackWalk64; +#endif + +#if defined( USE_CAPTURESTACKBACKTRACE ) + HMODULE m_hNTDllDll; + PFN_CaptureStackBackTrace m_pCaptureStackBackTrace; +#endif +}; +static CHelperFunctionsLoader s_HelperFunctions; + + + +#if defined( USE_STACKWALK64 ) //most reliable method thanks to boatloads of windows helper functions. Also the slowest. +int CrawlStack_StackWalk64( CONTEXT *pExceptionContext, void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + s_HelperFunctions.EnsureReady(); + + AUTO_LOCK( s_HelperFunctions.m_Mutex ); + + CONTEXT currentContext; + memcpy( ¤tContext, pExceptionContext, sizeof( CONTEXT ) ); + + STACKFRAME64 sfFrame = { 0 }; //memset(&sfFrame, 0x0, sizeof(sfFrame)); + sfFrame.AddrPC.Mode = sfFrame.AddrFrame.Mode = AddrModeFlat; +#ifdef _M_X64 + sfFrame.AddrPC.Offset = currentContext.Rip; + sfFrame.AddrFrame.Offset = currentContext.Rbp; // ???? +#else + sfFrame.AddrPC.Offset = currentContext.Eip; + sfFrame.AddrFrame.Offset = currentContext.Ebp; +#endif + + HANDLE hThread = GetCurrentThread(); + + int i; + for( i = 0; i != iSkipCount; ++i ) //skip entries that the requesting function thinks are uninformative + { + if(!s_HelperFunctions.m_pStackWalk64( STACKWALK64_MACHINETYPE, s_HelperFunctions.m_hProcess, hThread, &sfFrame, ¤tContext, NULL, NULL, NULL, NULL ) || + (sfFrame.AddrFrame.Offset == 0) ) + { + return 0; + } + } + + for( i = 0; i != iArrayCount; ++i ) + { + if(!s_HelperFunctions.m_pStackWalk64( STACKWALK64_MACHINETYPE, s_HelperFunctions.m_hProcess, hThread, &sfFrame, ¤tContext, NULL, NULL, NULL, NULL ) || + (sfFrame.AddrFrame.Offset == 0) ) + { + break; + } + pReturnAddressesOut[i] = (void *)sfFrame.AddrPC.Offset; + } + + return i; +} + +void GetCallStackReturnAddresses_Exception( void **CallStackReturnAddresses, int *pRetCount, int iSkipCount, _EXCEPTION_POINTERS * pExceptionInfo ) +{ + int iCount = CrawlStack_StackWalk64( pExceptionInfo->ContextRecord, CallStackReturnAddresses, *pRetCount, iSkipCount + 1 ); //skipping RaiseException() + *pRetCount = iCount; +} +#endif //#if defined( USE_STACKWALK64 ) + + + +int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + s_HelperFunctions.EnsureReady(); + + ++iSkipCount; //skip this function + +#if defined( USE_CAPTURESTACKBACKTRACE ) + if( s_HelperFunctions.m_pCaptureStackBackTrace != CaptureStackBackTrace_DummyFn ) + { + //docs state a total limit of 63 back traces between skipped and stored + int iRetVal = s_HelperFunctions.m_pCaptureStackBackTrace( iSkipCount, MIN( iArrayCount, 63 - iSkipCount ), pReturnAddressesOut, NULL ); + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, iRetVal ); + } +#endif +#if defined( USE_STACKWALK64 ) + if( s_HelperFunctions.m_pStackWalk64 != StackWalk64_DummyFn ) + { + int iInOutArrayCount = iArrayCount; //array count becomes both input and output with exception handler version + __try + { + ::RaiseException( 0, EXCEPTION_NONCONTINUABLE, 0, NULL ); + } + __except ( GetCallStackReturnAddresses_Exception( pReturnAddressesOut, &iInOutArrayCount, iSkipCount, GetExceptionInformation() ), EXCEPTION_EXECUTE_HANDLER ) + { + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, iInOutArrayCount ); + } + } +#endif + + return GetCallStack_Fast( pReturnAddressesOut, iArrayCount, iSkipCount ); +} + +void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) +{ + s_HelperFunctions.SetStackTranslationSymbolSearchPath( szSemicolonSeparatedList ); +} + +void StackToolsNotify_LoadedLibrary( const char *szLibName ) +{ + s_HelperFunctions.m_bShouldReloadSymbols = true; +} + +int TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style ) +{ + s_HelperFunctions.EnsureReady(); + tchar *szStartOutput = szOutput; + + if( szEntrySeparator == NULL ) + szEntrySeparator = _T(""); + + int iSeparatorSize = (int)strlen( szEntrySeparator ); + + for( int i = 0; i < iCallStackCount; ++i ) + { + if( !s_HelperFunctions.TranslatePointer( pCallStack[i], szOutput, iOutBufferSize, style ) ) + { + return i; + } + + int iLength = (int)strlen( szOutput ); + szOutput += iLength; + iOutBufferSize -= iLength; + + if( iOutBufferSize > iSeparatorSize ) + { + memcpy( szOutput, szEntrySeparator, iSeparatorSize * sizeof( tchar ) ); + szOutput += iSeparatorSize; + iOutBufferSize -= iSeparatorSize; + } + *szOutput = '\0'; + } + + szOutput -= iSeparatorSize; + if( szOutput >= szStartOutput ) + *szOutput = '\0'; + + return iCallStackCount; +} + +void PreloadStackInformation( void * const *pAddresses, int iAddressCount ) +{ + //nop on anything but 360 +} + +bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) +{ + s_HelperFunctions.EnsureReady(); + return s_HelperFunctions.GetFileAndLineFromAddress( pAddress, pFileNameOut, iMaxFileNameLength, iLineNumberOut, pDisplacementOut ); +} + +bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) +{ + s_HelperFunctions.EnsureReady(); + return s_HelperFunctions.GetSymbolNameFromAddress( pAddress, pSymbolNameOut, iMaxSymbolNameLength, pDisplacementOut ); +} + +bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) +{ + s_HelperFunctions.EnsureReady(); + return s_HelperFunctions.GetModuleNameFromAddress( pAddress, pModuleNameOut, iMaxModuleNameLength ); +} + +#else //#if defined( WIN32 ) && !defined( _X360 ) + +//=============================================================================================================== +// X360 version of the toolset +//=============================================================================================================== + +class C360StackTranslationHelper +{ +public: + C360StackTranslationHelper( void ) + { + m_bInitialized = true; + } + + ~C360StackTranslationHelper( void ) + { + StringSet_t::const_iterator iter; + + //module names + { + iter = m_ModuleNameSet.begin(); + while(iter != m_ModuleNameSet.end()) + { + char *pModuleName = (char*)(*iter); + delete []pModuleName; + iter++; + } + m_ModuleNameSet.clear(); + } + + //file names + { + iter = m_FileNameSet.begin(); + while(iter != m_FileNameSet.end()) + { + char *pFileName = (char*)(*iter); + delete []pFileName; + iter++; + } + m_FileNameSet.clear(); + } + + //symbol names + { + iter = m_SymbolNameSet.begin(); + while(iter != m_SymbolNameSet.end()) + { + char *pSymbolName = (char*)(*iter); + delete []pSymbolName; + iter++; + } + m_SymbolNameSet.clear(); + } + + m_bInitialized = false; + } + +private: + struct StackAddressInfo_t; +public: + + inline StackAddressInfo_t *CreateEntry( const FullStackInfo_t &info ) + { + std::pair retval = m_AddressInfoMap.insert( AddressInfoMapEntry_t( info.pAddress, StackAddressInfo_t() ) ); + if( retval.first->second.szModule != NULL ) + return &retval.first->second; //already initialized + + retval.first->second.iLine = info.iLine; + + + //share strings + + //module + { + const char *pModuleName; + StringSet_t::const_iterator iter = m_ModuleNameSet.find( info.szModuleName ); + if ( iter == m_ModuleNameSet.end() ) + { + int nLen = strlen(info.szModuleName) + 1; + pModuleName = new char [nLen]; + memcpy( (char *)pModuleName, info.szModuleName, nLen ); + m_ModuleNameSet.insert( pModuleName ); + } + else + { + pModuleName = (char *)(*iter); + } + + retval.first->second.szModule = pModuleName; + } + + //file + { + const char *pFileName; + StringSet_t::const_iterator iter = m_FileNameSet.find( info.szFileName ); + if ( iter == m_FileNameSet.end() ) + { + int nLen = strlen(info.szFileName) + 1; + pFileName = new char [nLen]; + memcpy( (char *)pFileName, info.szFileName, nLen ); + m_FileNameSet.insert( pFileName ); + } + else + { + pFileName = (char *)(*iter); + } + + retval.first->second.szFileName = pFileName; + } + + //symbol + { + const char *pSymbolName; + StringSet_t::const_iterator iter = m_SymbolNameSet.find( info.szSymbol ); + if ( iter == m_SymbolNameSet.end() ) + { + int nLen = strlen(info.szSymbol) + 1; + pSymbolName = new char [nLen]; + memcpy( (char *)pSymbolName, info.szSymbol, nLen ); + m_SymbolNameSet.insert( pSymbolName ); + } + else + { + pSymbolName = (char *)(*iter); + } + + retval.first->second.szSymbol = pSymbolName; + } + + return &retval.first->second; + } + + inline StackAddressInfo_t *FindInfoEntry( const void *pAddress ) + { + AddressInfoMapIter_t Iter = m_AddressInfoMap.find( pAddress ); + if( Iter != m_AddressInfoMap.end() ) + return &Iter->second; + + return NULL; + } + + inline int RetrieveStackInfo( const void * const *pAddresses, FullStackInfo_t *pReturnedStructs, int iAddressCount ) + { + int ReturnedTranslatedCount = -1; + //construct the message + // Header Finished Count(out) Input Count Input Array Returned data write address + int iMessageSize = 2 + sizeof( int * ) + sizeof( uint32 ) + (sizeof( void * ) * iAddressCount) + sizeof( FullStackInfo_t * ); + uint8 *pMessage = (uint8 *)stackalloc( iMessageSize ); + uint8 *pMessageWrite = pMessage; + pMessageWrite[0] = XBX_DBG_BNH_STACKTRANSLATOR; //have this message handled by stack translator handler + pMessageWrite[1] = ST_BHC_GETTRANSLATIONINFO; + pMessageWrite += 2; + *(int **)pMessageWrite = (int *)BigDWord( (DWORD)&ReturnedTranslatedCount ); + pMessageWrite += sizeof( int * ); + *(uint32 *)pMessageWrite = (uint32)BigDWord( (DWORD)iAddressCount ); + pMessageWrite += sizeof( uint32 ); + memcpy( pMessageWrite, pAddresses, iAddressCount * sizeof( void * ) ); + pMessageWrite += iAddressCount * sizeof( void * ); + *(FullStackInfo_t **)pMessageWrite = (FullStackInfo_t *)BigDWord( (DWORD)pReturnedStructs ); + bool bSuccess = XBX_SendBinaryData( pMessage, iMessageSize, false, 30000 ); + ReturnedTranslatedCount = BigDWord( ReturnedTranslatedCount ); + + if( bSuccess && (ReturnedTranslatedCount > 0) ) + { + return ReturnedTranslatedCount; + } + return 0; + } + + inline StackAddressInfo_t *CreateEntry( const void *pAddress ) + { + //ask VXConsole for information about the addresses we're clueless about + + FullStackInfo_t ReturnedData; + ReturnedData.pAddress = pAddress; + ReturnedData.szFileName[0] = '\0'; //strncpy( ReturnedData.szFileName, "FileUninitialized", sizeof( ReturnedData.szFileName ) ); + ReturnedData.szModuleName[0] = '\0'; //strncpy( ReturnedData.szModuleName, "ModuleUninitialized", sizeof( ReturnedData.szModuleName ) ); + ReturnedData.szSymbol[0] = '\0'; //strncpy( ReturnedData.szSymbol, "SymbolUninitialized", sizeof( ReturnedData.szSymbol ) ); + ReturnedData.iLine = 0; + ReturnedData.iSymbolOffset = 0; + + int iTranslated = RetrieveStackInfo( &pAddress, &ReturnedData, 1 ); + + if( iTranslated == 1 ) + { + //store + return CreateEntry( ReturnedData ); + } + + return FindInfoEntry( pAddress ); //probably won't work, but last ditch. + } + + inline StackAddressInfo_t *FindOrCreateEntry( const void *pAddress ) + { + StackAddressInfo_t *pReturn = FindInfoEntry( pAddress ); + if( pReturn == NULL ) + { + pReturn = CreateEntry( pAddress ); + } + + return pReturn; + } + + inline void LoadStackInformation( void * const *pAddresses, int iAddressCount ) + { + Assert( (iAddressCount > 0) && (pAddresses != NULL) ); + + int iNeedLoading = 0; + void **pNeedLoading = (void **)stackalloc( sizeof( const void * ) * iAddressCount ); //addresses we need to ask VXConsole about + + for( int i = 0; i != iAddressCount; ++i ) + { + if( FindInfoEntry( pAddresses[i] ) == NULL ) + { + //need to load this address + pNeedLoading[iNeedLoading] = pAddresses[i]; + ++iNeedLoading; + } + } + + if( iNeedLoading != 0 ) + { + //ask VXConsole for information about the addresses we're clueless about + FullStackInfo_t *pReturnedStructs = (FullStackInfo_t *)stackalloc( sizeof( FullStackInfo_t ) * iNeedLoading ); + + for( int i = 0; i < iNeedLoading; ++i ) + { + pReturnedStructs[i].pAddress = 0; + pReturnedStructs[i].szFileName[0] = '\0'; //strncpy( pReturnedStructs[i].szFileName, "FileUninitialized", sizeof( pReturnedStructs[i].szFileName ) ); + pReturnedStructs[i].szModuleName[0] = '\0'; //strncpy( pReturnedStructs[i].szModuleName, "ModuleUninitialized", sizeof( pReturnedStructs[i].szModuleName ) ); + pReturnedStructs[i].szSymbol[0] = '\0'; //strncpy( pReturnedStructs[i].szSymbol, "SymbolUninitialized", sizeof( pReturnedStructs[i].szSymbol ) ); + pReturnedStructs[i].iLine = 0; + pReturnedStructs[i].iSymbolOffset = 0; + } + + int iTranslated = RetrieveStackInfo( pNeedLoading, pReturnedStructs, iNeedLoading ); + + if( iTranslated == iNeedLoading ) + { + //store + for( int i = 0; i < iTranslated; ++i ) + { + CreateEntry( pReturnedStructs[i] ); + } + } + } + } + + inline bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) + { + StackAddressInfo_t *pInfo = FindOrCreateEntry( pAddress ); + if( pInfo && (pInfo->szFileName[0] != '\0') ) + { + strncpy( pFileNameOut, pInfo->szFileName, iMaxFileNameLength ); + iLineNumberOut = pInfo->iLine; + + if( pDisplacementOut ) + *pDisplacementOut = 0; //can't get line displacement on 360 + + return true; + } + + return false; + } + + inline bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) + { + StackAddressInfo_t *pInfo = FindOrCreateEntry( pAddress ); + if( pInfo && (pInfo->szSymbol[0] != '\0') ) + { + strncpy( pSymbolNameOut, pInfo->szSymbol, iMaxSymbolNameLength ); + + if( pDisplacementOut ) + *pDisplacementOut = pInfo->iSymbolOffset; + + return true; + } + + return false; + } + + inline bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) + { + StackAddressInfo_t *pInfo = FindOrCreateEntry( pAddress ); + if( pInfo && (pInfo->szModule[0] != '\0') ) + { + strncpy( pModuleNameOut, pInfo->szModule, iMaxModuleNameLength ); + return true; + } + + return false; + } + + + CThreadFastMutex m_hMutex; + +private: + +#pragma pack(push) +#pragma pack(1) + struct StackAddressInfo_t + { + StackAddressInfo_t( void ) : szModule(NULL), szFileName(NULL), szSymbol(NULL), iLine(0), iSymbolOffset(0) {} + const char *szModule; + const char *szFileName; + const char *szSymbol; + uint32 iLine; + uint32 iSymbolOffset; + }; +#pragma pack(pop) + + typedef std::map< const void *, StackAddressInfo_t, std::less> AddressInfoMap_t; + typedef AddressInfoMap_t::iterator AddressInfoMapIter_t; + typedef AddressInfoMap_t::value_type AddressInfoMapEntry_t; + + class CStringLess + { + public: + bool operator()(const char *pszLeft, const char *pszRight ) const + { + return ( V_tier0_stricmp( pszLeft, pszRight ) < 0 ); + } + }; + + typedef std::set StringSet_t; + + AddressInfoMap_t m_AddressInfoMap; //TODO: retire old entries? + StringSet_t m_ModuleNameSet; + StringSet_t m_FileNameSet; + StringSet_t m_SymbolNameSet; + bool m_bInitialized; + +}; +static C360StackTranslationHelper s_360StackTranslator; + + +int GetCallStack( void **pReturnAddressesOut, int iArrayCount, int iSkipCount ) +{ + ++iSkipCount; //skip this function + + //DmCaptureStackBackTrace() has no skip functionality, so we need to grab everything and skip within that list + void **pAllResults = (void **)stackalloc( sizeof( void * ) * (iArrayCount + iSkipCount) ); + if( DmCaptureStackBackTrace( iArrayCount + iSkipCount, pAllResults ) == XBDM_NOERR ) + { + for( int i = 0; i != iSkipCount; ++i ) + { + if( *pAllResults == NULL ) //DmCaptureStackBackTrace() NULL terminates the list instead of telling us how many were returned + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, 0 ); + + ++pAllResults; //move the pointer forward so the second loop indices match up + } + + for( int i = 0; i != iArrayCount; ++i ) + { + if( pAllResults[i] == NULL ) //DmCaptureStackBackTrace() NULL terminates the list instead of telling us how many were returned + return AppendParentStackTrace( pReturnAddressesOut, iArrayCount, i ); + + pReturnAddressesOut[i] = pAllResults[i]; + } + + return iArrayCount; //no room to append parent + } + + return GetCallStack_Fast( pReturnAddressesOut, iArrayCount, iSkipCount ); +} + + +void SetStackTranslationSymbolSearchPath( const char *szSemicolonSeparatedList ) +{ + //nop on 360 +} + + +void StackToolsNotify_LoadedLibrary( const char *szLibName ) +{ + //send off the notice to VXConsole + uint8 message[2]; + message[0] = XBX_DBG_BNH_STACKTRANSLATOR; //have this message handled by stack translator handler + message[1] = ST_BHC_LOADEDLIBARY; //loaded a library notification + XBX_SendBinaryData( message, 2 ); +} + + +int TranslateStackInfo( const void * const *pCallStack, int iCallStackCount, tchar *szOutput, int iOutBufferSize, const tchar *szEntrySeparator, TranslateStackInfo_StyleFlags_t style ) +{ + if( iCallStackCount == 0 ) + { + if( iOutBufferSize > 1 ) + *szOutput = '\0'; + + return 0; + } + + if( szEntrySeparator == NULL ) + szEntrySeparator = ""; + + int iSeparatorLength = strlen( szEntrySeparator ) + 1; + int iDataSize = (sizeof( void * ) * iCallStackCount) + (iSeparatorLength * sizeof( tchar )) + 1; //1 for style flags + + //360 is incapable of translation on it's own. Encode the stack for translation in VXConsole + //Encoded section is as such ":CSDECODE[encoded binary]" + int iEncodedSize = -EncodeBinaryToString( NULL, iDataSize, NULL, 0 ); //get needed buffer size + static const tchar cControlPrefix[] = XBX_CALLSTACKDECODEPREFIX; + const size_t cControlLength = (sizeof( cControlPrefix )/sizeof(tchar)) - 1; //-1 to remove null terminator + + if( iOutBufferSize > (iEncodedSize + (int)cControlLength + 2) ) //+2 for ']' and null term + { + COMPILE_TIME_ASSERT( TSISTYLEFLAG_LAST < (1<<8) ); //need to update the encoder/decoder to use more than a byte for style flags + + uint8 *pData = (uint8 *)stackalloc( iDataSize ); + pData[0] = (uint8)style; + memcpy( pData + 1, szEntrySeparator, iSeparatorLength * sizeof( tchar ) ); + memcpy( pData + 1 + (iSeparatorLength * sizeof( tchar )), pCallStack, iCallStackCount * sizeof( void * ) ); + + memcpy( szOutput, XBX_CALLSTACKDECODEPREFIX, cControlLength * sizeof( tchar ) ); + int iLength = cControlLength + EncodeBinaryToString( pData, iDataSize, &szOutput[cControlLength], (iOutBufferSize - cControlLength) ); + + szOutput[iLength] = ']'; + szOutput[iLength + 1] = '\0'; + } + + return iCallStackCount; +} + +void PreloadStackInformation( void * const *pAddresses, int iAddressCount ) +{ + AUTO_LOCK( s_360StackTranslator.m_hMutex ); + s_360StackTranslator.LoadStackInformation( pAddresses, iAddressCount ); +} + +bool GetFileAndLineFromAddress( const void *pAddress, tchar *pFileNameOut, int iMaxFileNameLength, uint32 &iLineNumberOut, uint32 *pDisplacementOut ) +{ + AUTO_LOCK( s_360StackTranslator.m_hMutex ); + return s_360StackTranslator.GetFileAndLineFromAddress( pAddress, pFileNameOut, iMaxFileNameLength, iLineNumberOut, pDisplacementOut ); +} + +bool GetSymbolNameFromAddress( const void *pAddress, tchar *pSymbolNameOut, int iMaxSymbolNameLength, uint64 *pDisplacementOut ) +{ + AUTO_LOCK( s_360StackTranslator.m_hMutex ); + return s_360StackTranslator.GetSymbolNameFromAddress( pAddress, pSymbolNameOut, iMaxSymbolNameLength, pDisplacementOut ); +} + +bool GetModuleNameFromAddress( const void *pAddress, tchar *pModuleNameOut, int iMaxModuleNameLength ) +{ + AUTO_LOCK( s_360StackTranslator.m_hMutex ); + return s_360StackTranslator.GetModuleNameFromAddress( pAddress, pModuleNameOut, iMaxModuleNameLength ); +} + +#endif //#else //#if defined( WIN32 ) && !defined( _X360 ) + +#endif //#if !defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + + + + +CCallStackStorage::CCallStackStorage( FN_GetCallStack GetStackFunction, uint32 iSkipCalls ) +{ + iValidEntries = GetStackFunction( pStack, ARRAYSIZE( pStack ), iSkipCalls + 1 ); +} + + + +CStackTop_CopyParentStack::CStackTop_CopyParentStack( void * const *pParentStackTrace, int iParentStackTraceLength ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + //miniature version of GetCallStack_Fast() +#if (defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) &&\ + (defined( WIN32 ) && !defined( _X360 ) && !defined(_M_X64)) + void *pStackCrawlEBP; + __asm + { + mov [pStackCrawlEBP], ebp; + } + pStackCrawlEBP = *(void **)pStackCrawlEBP; + m_pReplaceAddress = *((void **)pStackCrawlEBP + 1); + m_pStackBase = (void *)((void **)pStackCrawlEBP + 1); +#else + m_pReplaceAddress = NULL; + m_pStackBase = this; +#endif + + m_pParentStackTrace = NULL; + + if( (pParentStackTrace != NULL) && (iParentStackTraceLength > 0) ) + { + while( (iParentStackTraceLength > 0) && (pParentStackTrace[iParentStackTraceLength - 1] == NULL) ) + { + --iParentStackTraceLength; + } + + if( iParentStackTraceLength > 0 ) + { + m_pParentStackTrace = new void * [iParentStackTraceLength]; + memcpy( (void **)m_pParentStackTrace, pParentStackTrace, sizeof( void * ) * iParentStackTraceLength ); + } + } + + m_iParentStackTraceLength = iParentStackTraceLength; + + m_pPrevTop = g_StackTop; + g_StackTop = this; + Assert( (CStackTop_Base *)g_StackTop == this ); +#endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) +} + +CStackTop_CopyParentStack::~CStackTop_CopyParentStack( void ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + Assert( (CStackTop_Base *)g_StackTop == this ); + g_StackTop = m_pPrevTop; + + if( m_pParentStackTrace != NULL ) + { + delete []m_pParentStackTrace; + } +#endif +} + + + +CStackTop_ReferenceParentStack::CStackTop_ReferenceParentStack( void * const *pParentStackTrace, int iParentStackTraceLength ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + //miniature version of GetCallStack_Fast() +#if (defined( TIER0_FPO_DISABLED ) || defined( _DEBUG )) &&\ + (defined( WIN32 ) && !defined( _X360 ) && !defined(_M_X64)) + void *pStackCrawlEBP; + __asm + { + mov [pStackCrawlEBP], ebp; + } + pStackCrawlEBP = *(void **)pStackCrawlEBP; + m_pReplaceAddress = *((void **)pStackCrawlEBP + 1); + m_pStackBase = (void *)((void **)pStackCrawlEBP + 1); +#else + m_pReplaceAddress = NULL; + m_pStackBase = this; +#endif + + m_pParentStackTrace = pParentStackTrace; + + if( (pParentStackTrace != NULL) && (iParentStackTraceLength > 0) ) + { + while( (iParentStackTraceLength > 0) && (pParentStackTrace[iParentStackTraceLength - 1] == NULL) ) + { + --iParentStackTraceLength; + } + } + + m_iParentStackTraceLength = iParentStackTraceLength; + + m_pPrevTop = g_StackTop; + g_StackTop = this; + Assert( (CStackTop_Base *)g_StackTop == this ); +#endif //#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) +} + +CStackTop_ReferenceParentStack::~CStackTop_ReferenceParentStack( void ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + Assert( (CStackTop_Base *)g_StackTop == this ); + g_StackTop = m_pPrevTop; + + ReleaseParentStackReferences(); +#endif +} + +void CStackTop_ReferenceParentStack::ReleaseParentStackReferences( void ) +{ +#if defined( ENABLE_RUNTIME_STACK_TRANSLATION ) + m_pParentStackTrace = NULL; + m_iParentStackTraceLength = 0; +#endif +} + + + + +//Encodes data so that every byte's most significant bit is a 1. Ensuring no null terminators. +//This puts the encoded data in the 128-255 value range. Leaving all standard ascii characters for control. +//Returns string length (not including the written null terminator as is standard). +//Or if the buffer is too small. Returns negative of necessary buffer size (including room needed for null terminator) +int EncodeBinaryToString( const void *pToEncode, int iDataLength, char *pEncodeOut, int iEncodeBufferSize ) +{ + int iEncodedSize = iDataLength; + iEncodedSize += (iEncodedSize + 6) / 7; //Have 1 control byte for every 7 actual bytes + iEncodedSize += sizeof( uint32 ) + 1; //data size at the beginning of the blob and null terminator at the end + + if( (iEncodedSize > iEncodeBufferSize) || (pEncodeOut == NULL) || (pToEncode == NULL) ) + return -iEncodedSize; //not enough room + + uint8 *pEncodeWrite = (uint8 *)pEncodeOut; + + //first encode the data size. Encodes lowest 28 bits and discards the high 4 + pEncodeWrite[0] = ((iDataLength >> 21) & 0xFF) | 0x80; + pEncodeWrite[1] = ((iDataLength >> 14) & 0xFF) | 0x80; + pEncodeWrite[2] = ((iDataLength >> 7) & 0xFF) | 0x80; + pEncodeWrite[3] = ((iDataLength >> 0) & 0xFF) | 0x80; + pEncodeWrite += 4; + + const uint8 *pEncodeRead = (const uint8 *)pToEncode; + const uint8 *pEncodeStop = pEncodeRead + iDataLength; + uint8 *pEncodeWriteLastControlByte = pEncodeWrite; + int iControl = 0; + + //Encode the data + while( pEncodeRead < pEncodeStop ) + { + if( iControl == 0 ) + { + pEncodeWriteLastControlByte = pEncodeWrite; + *pEncodeWriteLastControlByte = 0x80; + } + else + { + *pEncodeWrite = *pEncodeRead | 0x80; //encoded data always has the MSB bit set (cheap avoidance of null terminators) + *pEncodeWriteLastControlByte |= (((*pEncodeRead) & 0x80) ^ 0x80) >> iControl; //We use the control byte to XOR the MSB back to original values on decode + ++pEncodeRead; + } + + ++pEncodeWrite; + ++iControl; + iControl &= 7; //8->0 + } + *pEncodeWrite = '\0'; + + return iEncodedSize - 1; +} + +//Decodes a string produced by EncodeBinaryToString(). Safe to decode in place if you don't mind trashing your string, binary byte count always less than string byte count. +//Returns: +// >= 0 is the decoded data size +// INT_MIN (most negative value possible) indicates an improperly formatted string (not our data) +// all other negative values are the negative of how much dest buffer size is necessary. +int DecodeBinaryFromString( const char *pString, void *pDestBuffer, int iDestBufferSize, char **ppParseFinishOut ) +{ + const uint8 *pDecodeRead = (const uint8 *)pString; + + if( (pDecodeRead[0] < 0x80) || (pDecodeRead[1] < 0x80) || (pDecodeRead[2] < 0x80) || (pDecodeRead[3] < 0x80) ) + { + if( ppParseFinishOut != NULL ) + *ppParseFinishOut = (char *)pString; + + return INT_MIN; //Don't know what the string is, but it's not our format + } + + int iDecodedSize = 0; + iDecodedSize |= (pDecodeRead[0] & 0x7F) << 21; + iDecodedSize |= (pDecodeRead[1] & 0x7F) << 14; + iDecodedSize |= (pDecodeRead[2] & 0x7F) << 7; + iDecodedSize |= (pDecodeRead[3] & 0x7F) << 0; + pDecodeRead += 4; + + int iTextLength = iDecodedSize; + iTextLength += (iTextLength + 6) / 7; //Have 1 control byte for every 7 actual bytes + + //make sure it's formatted properly + for( int i = 0; i != iTextLength; ++i ) + { + if( pDecodeRead[i] < 0x80 ) //encoded data always has MSB set + { + if( ppParseFinishOut != NULL ) + *ppParseFinishOut = (char *)pString; + + return INT_MIN; //either not our data, or part of the string is missing + } + } + + if( iDestBufferSize < iDecodedSize ) + { + if( ppParseFinishOut != NULL ) + *ppParseFinishOut = (char *)pDecodeRead; + + return -iDecodedSize; //dest buffer not big enough to hold the data + } + + const uint8 *pStopDecoding = pDecodeRead + iTextLength; + uint8 *pDecodeWrite = (uint8 *)pDestBuffer; + int iControl = 0; + int iLSBXOR = 0; + + while( pDecodeRead < pStopDecoding ) + { + if( iControl == 0 ) + { + iLSBXOR = *pDecodeRead; + } + else + { + *pDecodeWrite = *pDecodeRead ^ ((iLSBXOR << iControl) & 0x80); + ++pDecodeWrite; + } + + ++pDecodeRead; + ++iControl; + iControl &= 7; //8->0 + } + + if( ppParseFinishOut != NULL ) + *ppParseFinishOut = (char *)pDecodeRead; + + return iDecodedSize; +} + + diff --git a/tier0/systeminformation.cpp b/tier0/systeminformation.cpp new file mode 100644 index 0000000..c26a2d1 --- /dev/null +++ b/tier0/systeminformation.cpp @@ -0,0 +1,310 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "pch_tier0.h" +#include "tier0/platform.h" +#include "tier0/systeminformation.h" + +#ifdef IS_WINDOWS_PC +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PrivateType( xxx ) ValvePrivateType_##xxx + + typedef enum { SystemPerformanceInformation = 2 } + PrivateType( SYSTEM_INFORMATION_CLASS ); + + typedef LONG PrivateType( NTSTATUS ); + + typedef PrivateType( NTSTATUS ) ( WINAPI * PrivateType( NtQuerySystemInformation ) ) + ( + /*IN*/ PrivateType( SYSTEM_INFORMATION_CLASS ) SystemInformationClass, + /*OUT*/ PVOID SystemInformation, + /*IN*/ ULONG SystemInformationLength, + /*OUT*/ PULONG ReturnLength /*OPTIONAL*/ + ); + + typedef struct + { + LARGE_INTEGER IdleProcessTime; + LARGE_INTEGER IoTransferCount[3]; + ULONG IoOperationCount[3]; + ULONG AvailablePages; + ULONG CommittedPages; + ULONG CommitLimit; + ULONG u00683; + ULONG u00684; + ULONG u00685; + ULONG u00686; + ULONG u00687; + ULONG u00688; + ULONG u00689; + ULONG u00690; + ULONG u00691; + ULONG u00692; + ULONG u00693; + ULONG u00694; + ULONG u00695; + ULONG u00696; + ULONG PagedPoolPages; + ULONG NonPagedPoolPages; + ULONG PagedPoolAllocs; + ULONG PagedPoolFrees; + ULONG NonPagedPoolAllocs; + ULONG NonPagedPoolFrees; + ULONG FreeSystemPtes; + ULONG u00704; + ULONG u00705; + ULONG u00706; + ULONG NonPagedPoolLookasideHits; + ULONG PagedPoolLookasideHits; + ULONG FreePagedPoolPages; + ULONG u00710; + ULONG u00711; + ULONG u00712; + ULONG uCounters[34]; + } + PrivateType( SYSTEM_PERFORMANCE_INFORMATION ); + +#ifdef __cplusplus +} +#endif + +// +// Cached information about a dll proc +// +class CSysCallCacheEntry +{ +public: + CSysCallCacheEntry(); + ~CSysCallCacheEntry(); + +public: + bool IsInitialized() const; + SYSTEM_CALL_RESULT_t CallResult() const; + + SYSTEM_CALL_RESULT_t InitializeLoadModule( _TCHAR *pszModule, char *pszFunction ); + SYSTEM_CALL_RESULT_t InitializeFindModule( _TCHAR *pszModule, char *pszFunction ); + SYSTEM_CALL_RESULT_t InitializeFindProc( HMODULE hModule, char *pszFunction ); + + void SetFailed( SYSTEM_CALL_RESULT_t eResult ); + void Reset(); + + template < typename FN > + FN GetFunction() const; + +protected: + SYSTEM_CALL_RESULT_t m_eResult; + FARPROC m_pfnSysCall; + HMODULE m_hModule; + bool m_bInitialized; + bool m_bFreeModule; +}; + +struct CSysCallCacheEntry_LoadModule : public CSysCallCacheEntry +{ + CSysCallCacheEntry_LoadModule( _TCHAR *pszModule, char *pszFunction ) { InitializeLoadModule( pszModule, pszFunction ); } +}; +struct CSysCallCacheEntry_FindModule : public CSysCallCacheEntry +{ + CSysCallCacheEntry_FindModule( _TCHAR *pszModule, char *pszFunction ) { InitializeFindModule( pszModule, pszFunction ); } +}; +struct CSysCallCacheEntry_FindProc : public CSysCallCacheEntry +{ + CSysCallCacheEntry_FindProc( HMODULE hModule, char *pszFunction ) { InitializeFindProc( hModule, pszFunction ); } +}; + + + +CSysCallCacheEntry::CSysCallCacheEntry() : + m_eResult( SYSCALL_SUCCESS ), + m_pfnSysCall( NULL ), + m_hModule( NULL ), + m_bInitialized( false ), + m_bFreeModule( false ) +{ +} + +CSysCallCacheEntry::~CSysCallCacheEntry() +{ + Reset(); +} + +bool CSysCallCacheEntry::IsInitialized() const +{ + return m_bInitialized; +} + +SYSTEM_CALL_RESULT_t CSysCallCacheEntry::CallResult() const +{ + return m_eResult; +} + +SYSTEM_CALL_RESULT_t CSysCallCacheEntry::InitializeLoadModule( _TCHAR *pszModule, char *pszFunction ) +{ + m_bInitialized = true; + + m_hModule = ::LoadLibrary( pszModule ); + m_bFreeModule = true; + if ( !m_hModule ) + return m_eResult = SYSCALL_NODLL; + + return InitializeFindProc( m_hModule, pszFunction ); +} + +SYSTEM_CALL_RESULT_t CSysCallCacheEntry::InitializeFindModule( _TCHAR *pszModule, char *pszFunction ) +{ + m_bInitialized = true; + + m_hModule = ::GetModuleHandle( pszModule ); + m_bFreeModule = false; + if ( !m_hModule ) + return m_eResult = SYSCALL_NODLL; + + return InitializeFindProc( m_hModule, pszFunction ); +} + +SYSTEM_CALL_RESULT_t CSysCallCacheEntry::InitializeFindProc( HMODULE hModule, char *pszFunction ) +{ + m_bInitialized = true; + + m_pfnSysCall = GetProcAddress( hModule, pszFunction ); + if ( !m_pfnSysCall ) + return m_eResult = SYSCALL_NOPROC; + + return m_eResult = SYSCALL_SUCCESS; +} + +void CSysCallCacheEntry::Reset() +{ + if ( m_bInitialized ) + { + if ( m_bFreeModule && m_hModule ) + ::FreeLibrary( m_hModule ); + m_eResult = SYSCALL_SUCCESS; + m_hModule = NULL; + m_pfnSysCall = NULL; + m_bFreeModule = false; + m_bInitialized = false; + } +} + +void CSysCallCacheEntry::SetFailed( SYSTEM_CALL_RESULT_t eResult ) +{ + m_eResult = eResult; +} + +template < typename FN > +FN CSysCallCacheEntry::GetFunction() const +{ + return reinterpret_cast< FN >( m_pfnSysCall ); +} + + + +// +// Plat_GetMemPageSize +// Returns the size of a memory page in bytes. +// +unsigned long Plat_GetMemPageSize() +{ + return 4; // On 32-bit systems memory page size is 4 Kb +} + +// +// Plat_GetPagedPoolInfo +// Fills in the paged pool info structure if successful. +// +SYSTEM_CALL_RESULT_t Plat_GetPagedPoolInfo( PAGED_POOL_INFO_t *pPPI ) +{ + memset( pPPI, 0, sizeof( *pPPI ) ); + + static CSysCallCacheEntry_FindModule qsi( _T( "ntdll.dll" ), "NtQuerySystemInformation" ); + + if ( qsi.CallResult() != SYSCALL_SUCCESS ) + return qsi.CallResult(); + + static bool s_bOsVersionValid = false; + if ( !s_bOsVersionValid ) + { + s_bOsVersionValid = true; + OSVERSIONINFO osver; + memset( &osver, 0, sizeof( osver ) ); + osver.dwOSVersionInfoSize = sizeof( osver ); + GetVersionEx( &osver ); + + // We should run it only on Windows XP or Windows 2003 +#define MAKEVER( high, low ) DWORD( MAKELONG( low, high ) ) + DWORD dwOsVer = MAKEVER( osver.dwMajorVersion, osver.dwMinorVersion ); + if ( dwOsVer < MAKEVER( 5, 1 ) || // Earlier than WinXP + dwOsVer > MAKEVER( 5, 2 ) ) // Later than Win2003 (or 64-bit) + { + qsi.SetFailed( SYSCALL_UNSUPPORTED ); + } + + // Don't care for 64-bit Windows + CSysCallCacheEntry_FindModule wow64( _T( "kernel32.dll" ), "IsWow64Process" ); + if ( wow64.CallResult() == SYSCALL_SUCCESS ) + { + typedef BOOL ( WINAPI * PFNWOW64 )( HANDLE, PBOOL ); + BOOL b64 = FALSE; + if ( ( wow64.GetFunction< PFNWOW64 >() )( GetCurrentProcess(), &b64 ) && + b64 ) + { + qsi.SetFailed( SYSCALL_UNSUPPORTED ); + } + } + + if ( qsi.CallResult() != SYSCALL_SUCCESS ) + return qsi.CallResult(); + } + + // Invoke proc + PrivateType( SYSTEM_PERFORMANCE_INFORMATION ) spi = {}; + ULONG ulLength = sizeof( spi ); + PrivateType( NTSTATUS ) lResult = + ( qsi.GetFunction< PrivateType( NtQuerySystemInformation ) >() ) + ( SystemPerformanceInformation, &spi, ulLength, &ulLength ); + if ( lResult ) + return SYSCALL_FAILED; + + // Return the result + pPPI->numPagesUsed = spi.PagedPoolPages; + pPPI->numPagesFree = spi.FreePagedPoolPages; + return SYSCALL_SUCCESS; +} + + +#else + + +// +// Plat_GetMemPageSize +// Returns the size of a memory page in bytes. +// +unsigned long Plat_GetMemPageSize() +{ + return 4; // Assume unknown page size is 4 Kb +} + +// +// Plat_GetPagedPoolInfo +// Fills in the paged pool info structure if successful. +// +SYSTEM_CALL_RESULT_t Plat_GetPagedPoolInfo( PAGED_POOL_INFO_t *pPPI ) +{ + memset( pPPI, 0, sizeof( *pPPI ) ); + return SYSCALL_UNSUPPORTED; +} + + +#endif \ No newline at end of file diff --git a/tier0/thread.cpp b/tier0/thread.cpp new file mode 100644 index 0000000..9884385 --- /dev/null +++ b/tier0/thread.cpp @@ -0,0 +1,219 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Thread management routines +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" + +#include "tier0/valve_off.h" +#ifdef _WIN32 +#define WIN_32_LEAN_AND_MEAN +#include +#include +#include +#endif + +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "tier0/threadtools.h" + +unsigned long Plat_GetCurrentThreadID() +{ + return ThreadGetCurrentId(); +} + + +#if defined(_WIN32) && defined(_M_IX86) + +static CThreadMutex s_BreakpointStateMutex; + +struct X86HardwareBreakpointState_t +{ + const void *pAddress[4]; + char nWatchBytes[4]; + bool bBreakOnRead[4]; +}; +static X86HardwareBreakpointState_t s_BreakpointState = { {0,0,0,0}, {0,0,0,0}, {false,false,false,false} }; + +static void X86ApplyBreakpointsToThread( DWORD dwThreadId ) +{ + CONTEXT ctx; + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + X86HardwareBreakpointState_t *pState = &s_BreakpointState; + ctx.Dr0 = (DWORD) pState->pAddress[0]; + ctx.Dr1 = (DWORD) pState->pAddress[1]; + ctx.Dr2 = (DWORD) pState->pAddress[2]; + ctx.Dr3 = (DWORD) pState->pAddress[3]; + ctx.Dr7 = (DWORD) 0; + for ( int i = 0; i < 4; ++i ) + { + if ( pState->pAddress[i] && pState->nWatchBytes[i] ) + { + ctx.Dr7 |= 1 << (i*2); + if ( pState->bBreakOnRead[i] ) + ctx.Dr7 |= 3 << (16 + i*4); + else + ctx.Dr7 |= 1 << (16 + i*4); + switch ( pState->nWatchBytes[i] ) + { + case 1: ctx.Dr7 |= 0<<(18 + i*4); break; + case 2: ctx.Dr7 |= 1<<(18 + i*4); break; + case 4: ctx.Dr7 |= 3<<(18 + i*4); break; + case 8: ctx.Dr7 |= 2<<(18 + i*4); break; + } + } + } + + // Freeze this thread, adjust its breakpoint state + HANDLE hThread = OpenThread( THREAD_SUSPEND_RESUME | THREAD_SET_CONTEXT, FALSE, dwThreadId ); + if ( hThread != INVALID_HANDLE_VALUE ) + { + if ( SuspendThread( hThread ) != -1 ) + { + SetThreadContext( hThread, &ctx ); + ResumeThread( hThread ); + } + CloseHandle( hThread ); + } +} + +static DWORD STDCALL ThreadProcX86SetDataBreakpoints( LPVOID pvParam ) +{ + if ( pvParam ) + { + X86ApplyBreakpointsToThread( *(unsigned long*)pvParam ); + return 0; + } + + // This function races against creation and destruction of new threads. Try to execute as quickly as possible. + SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST ); + + DWORD dwProcId = GetCurrentProcessId(); + DWORD dwThisThreadId = GetCurrentThreadId(); + HANDLE hSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); + if ( hSnap != INVALID_HANDLE_VALUE ) + { + THREADENTRY32 threadEntry; + // Thread32First/Thread32Next may adjust dwSize to be smaller. It's weird. Read the doc. + const DWORD dwMinSize = (char*)(&threadEntry.th32OwnerProcessID + 1) - (char*)&threadEntry; + + threadEntry.dwSize = sizeof( THREADENTRY32 ); + BOOL bContinue = Thread32First( hSnap, &threadEntry ); + while ( bContinue ) + { + if ( threadEntry.dwSize >= dwMinSize ) + { + if ( threadEntry.th32OwnerProcessID == dwProcId && threadEntry.th32ThreadID != dwThisThreadId ) + { + X86ApplyBreakpointsToThread( threadEntry.th32ThreadID ); + } + } + + threadEntry.dwSize = sizeof( THREADENTRY32 ); + bContinue = Thread32Next( hSnap, &threadEntry ); + } + + CloseHandle( hSnap ); + } + return 0; +} + +void Plat_SetHardwareDataBreakpoint( const void *pAddress, int nWatchBytes, bool bBreakOnRead ) +{ + Assert( pAddress ); + Assert( nWatchBytes == 0 || nWatchBytes == 1 || nWatchBytes == 2 || nWatchBytes == 4 || nWatchBytes == 8 ); + + s_BreakpointStateMutex.Lock(); + + if ( nWatchBytes == 0 ) + { + for ( int i = 0; i < 4; ++i ) + { + if ( pAddress == s_BreakpointState.pAddress[i] ) + { + for ( ; i < 3; ++i ) + { + s_BreakpointState.pAddress[i] = s_BreakpointState.pAddress[i+1]; + s_BreakpointState.nWatchBytes[i] = s_BreakpointState.nWatchBytes[i+1]; + s_BreakpointState.bBreakOnRead[i] = s_BreakpointState.bBreakOnRead[i+1]; + } + s_BreakpointState.pAddress[3] = NULL; + s_BreakpointState.nWatchBytes[3] = 0; + s_BreakpointState.bBreakOnRead[3] = false; + break; + } + } + } + else + { + // Replace first null entry or first existing entry at this address, or bump all entries down + for ( int i = 0; i < 4; ++i ) + { + if ( s_BreakpointState.pAddress[i] && s_BreakpointState.pAddress[i] != pAddress && i < 3 ) + continue; + + // Last iteration. + + if ( s_BreakpointState.pAddress[i] && s_BreakpointState.pAddress[i] != pAddress ) + { + // Full up. Shift table down, drop least recently set + for ( int j = 0; j < 3; ++j ) + { + s_BreakpointState.pAddress[j] = s_BreakpointState.pAddress[j+1]; + s_BreakpointState.nWatchBytes[j] = s_BreakpointState.nWatchBytes[j+1]; + s_BreakpointState.bBreakOnRead[j] = s_BreakpointState.bBreakOnRead[j+1]; + } + } + s_BreakpointState.pAddress[i] = pAddress; + s_BreakpointState.nWatchBytes[i] = nWatchBytes; + s_BreakpointState.bBreakOnRead[i] = bBreakOnRead; + break; + } + } + + + HANDLE hWorkThread = CreateThread( NULL, NULL, &ThreadProcX86SetDataBreakpoints, NULL, 0, NULL ); + if ( hWorkThread != INVALID_HANDLE_VALUE ) + { + WaitForSingleObject( hWorkThread, INFINITE ); + CloseHandle( hWorkThread ); + } + + s_BreakpointStateMutex.Unlock(); +} + +void Plat_ApplyHardwareDataBreakpointsToNewThread( unsigned long dwThreadID ) +{ + s_BreakpointStateMutex.Lock(); + if ( dwThreadID != GetCurrentThreadId() ) + { + X86ApplyBreakpointsToThread( dwThreadID ); + } + else + { + HANDLE hWorkThread = CreateThread( NULL, NULL, &ThreadProcX86SetDataBreakpoints, &dwThreadID, 0, NULL ); + if ( hWorkThread != INVALID_HANDLE_VALUE ) + { + WaitForSingleObject( hWorkThread, INFINITE ); + CloseHandle( hWorkThread ); + } + + } + s_BreakpointStateMutex.Unlock(); +} + +#else + +void Plat_SetHardwareDataBreakpoint( const void *pAddress, int nWatchBytes, bool bBreakOnRead ) +{ + // no impl on this platform yet +} + +void Plat_ApplyHardwareDataBreakpointsToNewThread( unsigned long dwThreadID ) +{ + // no impl on this platform yet +} + +#endif diff --git a/tier0/threadtools.cpp b/tier0/threadtools.cpp new file mode 100644 index 0000000..001e703 --- /dev/null +++ b/tier0/threadtools.cpp @@ -0,0 +1,2404 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "pch_tier0.h" + +#include "tier1/strtools.h" +#include "tier0/dynfunction.h" +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN32_LEAN_AND_MEAN +#include +#endif +#ifdef _WIN32 + #include + +#ifdef IS_WINDOWS_PC + #include + #pragma comment(lib, "winmm.lib") +#endif // IS_WINDOWS_PC + +#elif defined(POSIX) + +#if !defined(OSX) + #include + #include + #define sem_unlink( arg ) + #define OS_TO_PTHREAD(x) (x) +#else + #define pthread_yield pthread_yield_np + #include + #include + #define OS_TO_PTHREAD(x) pthread_from_mach_thread_np( x ) +#endif // !OSX + +#ifdef LINUX +#include // RTLD_NEXT +#endif + +typedef int (*PTHREAD_START_ROUTINE)( + void *lpThreadParameter + ); +typedef PTHREAD_START_ROUTINE LPTHREAD_START_ROUTINE; +#include +#include +#include +#include +#include +#include +#define GetLastError() errno +typedef void *LPVOID; +#endif + +#include "tier0/valve_minmax_off.h" +#include +#include "tier0/valve_minmax_on.h" + +#include "tier0/threadtools.h" +#include "tier0/vcrmode.h" + +#ifdef _X360 +#include "xbox/xbox_win32stubs.h" +#endif + +#include "tier0/vprof_telemetry.h" + +// Must be last header... +#include "tier0/memdbgon.h" + +#define THREADS_DEBUG 1 + +// Need to ensure initialized before other clients call in for main thread ID +#ifdef _WIN32 +#pragma warning(disable:4073) +#pragma init_seg(lib) +#endif + +#ifdef _WIN32 +ASSERT_INVARIANT(TT_SIZEOF_CRITICALSECTION == sizeof(CRITICAL_SECTION)); +ASSERT_INVARIANT(TT_INFINITE == INFINITE); +#endif + +//----------------------------------------------------------------------------- +// Simple thread functions. +// Because _beginthreadex uses stdcall, we need to convert to cdecl +//----------------------------------------------------------------------------- +struct ThreadProcInfo_t +{ + ThreadProcInfo_t( ThreadFunc_t pfnThread, void *pParam ) + : pfnThread( pfnThread), + pParam( pParam ) + { + } + + ThreadFunc_t pfnThread; + void * pParam; +}; + +//--------------------------------------------------------- + +#ifdef _WIN32 +static unsigned __stdcall ThreadProcConvert( void *pParam ) +#elif defined(POSIX) +static void *ThreadProcConvert( void *pParam ) +#else +#error +#endif +{ + ThreadProcInfo_t info = *((ThreadProcInfo_t *)pParam); + delete ((ThreadProcInfo_t *)pParam); +#ifdef _WIN32 + return (*info.pfnThread)(info.pParam); +#elif defined(POSIX) + return (void *)(*info.pfnThread)(info.pParam); +#else +#error +#endif +} + + +//--------------------------------------------------------- + +ThreadHandle_t CreateSimpleThread( ThreadFunc_t pfnThread, void *pParam, ThreadId_t *pID, unsigned stackSize ) +{ +#ifdef _WIN32 + ThreadId_t idIgnored; + if ( !pID ) + pID = &idIgnored; + HANDLE h = VCRHook_CreateThread(NULL, stackSize, (LPTHREAD_START_ROUTINE)ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ), CREATE_SUSPENDED, pID); + if ( h != INVALID_HANDLE_VALUE ) + { + Plat_ApplyHardwareDataBreakpointsToNewThread( *pID ); + ResumeThread( h ); + } + return (ThreadHandle_t)h; +#elif defined(POSIX) + pthread_t tid; + + // If we need to create threads that are detached right out of the gate, we would need to do something like this: + // pthread_attr_t attr; + // int rc = pthread_attr_init(&attr); + // rc = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + // ... pthread_create( &tid, &attr, ... ) ... + // rc = pthread_attr_destroy(&attr); + // ... pthread_join will now fail + + int ret = pthread_create( &tid, NULL, ThreadProcConvert, new ThreadProcInfo_t( pfnThread, pParam ) ); + if ( ret ) + { + // There are only PTHREAD_THREADS_MAX number of threads, and we're probably leaking handles if ret == EAGAIN here? + Error( "CreateSimpleThread: pthread_create failed. Someone not calling pthread_detach() or pthread_join. Ret:%d\n", ret ); + } + if ( pID ) + *pID = (ThreadId_t)tid; + Plat_ApplyHardwareDataBreakpointsToNewThread( (long unsigned int)tid ); + return (ThreadHandle_t)tid; +#endif +} + +ThreadHandle_t CreateSimpleThread( ThreadFunc_t pfnThread, void *pParam, unsigned stackSize ) +{ + return CreateSimpleThread( pfnThread, pParam, NULL, stackSize ); +} + +PLATFORM_INTERFACE void ThreadDetach( ThreadHandle_t hThread ) +{ +#if defined( POSIX ) + // The resources of this thread will be freed immediately when it terminates, + // instead of waiting for another thread to perform PTHREAD_JOIN. + pthread_t tid = ( pthread_t )hThread; + + pthread_detach( tid ); +#endif +} + +bool ReleaseThreadHandle( ThreadHandle_t hThread ) +{ +#ifdef _WIN32 + return ( CloseHandle( hThread ) != 0 ); +#else + return true; +#endif +} + +//----------------------------------------------------------------------------- +// +// Wrappers for other simple threading operations +// +//----------------------------------------------------------------------------- + +void ThreadSleep(unsigned nMilliseconds) +{ +#ifdef _WIN32 + +#ifdef IS_WINDOWS_PC + static bool bInitialized = false; + if ( !bInitialized ) + { + bInitialized = true; + // Set the timer resolution to 1 ms (default is 10.0, 15.6, 2.5, 1.0 or + // some other value depending on hardware and software) so that we can + // use Sleep( 1 ) to avoid wasting CPU time without missing our frame + // rate. + timeBeginPeriod( 1 ); + } +#endif // IS_WINDOWS_PC + + Sleep( nMilliseconds ); +#elif defined(POSIX) + usleep( nMilliseconds * 1000 ); +#endif +} + +//----------------------------------------------------------------------------- + +#ifndef ThreadGetCurrentId +uint ThreadGetCurrentId() +{ +#ifdef _WIN32 + return GetCurrentThreadId(); +#elif defined(POSIX) + return (uint)pthread_self(); +#endif +} +#endif + +//----------------------------------------------------------------------------- +ThreadHandle_t ThreadGetCurrentHandle() +{ +#ifdef _WIN32 + return (ThreadHandle_t)GetCurrentThread(); +#elif defined(POSIX) + return (ThreadHandle_t)pthread_self(); +#endif +} + +// On PS3, this will return true for zombie threads +bool ThreadIsThreadIdRunning( ThreadId_t uThreadId ) +{ +#ifdef _WIN32 + bool bRunning = true; + HANDLE hThread = ::OpenThread( THREAD_QUERY_INFORMATION , false, uThreadId ); + if ( hThread ) + { + DWORD dwExitCode; + if( !::GetExitCodeThread( hThread, &dwExitCode ) || dwExitCode != STILL_ACTIVE ) + bRunning = false; + + CloseHandle( hThread ); + } + else + { + bRunning = false; + } + return bRunning; +#elif defined( _PS3 ) + + // will return CELL_OK for zombie threads + int priority; + return (sys_ppu_thread_get_priority( uThreadId, &priority ) == CELL_OK ); + +#elif defined(POSIX) + pthread_t thread = OS_TO_PTHREAD(uThreadId); + if ( thread ) + { + int iResult = pthread_kill( thread, 0 ); + if ( iResult == 0 ) + return true; + } + else + { + // We really ought not to be passing NULL in to here + AssertMsg( false, "ThreadIsThreadIdRunning received a null thread ID" ); + } + + return false; +#endif +} + +//----------------------------------------------------------------------------- + +int ThreadGetPriority( ThreadHandle_t hThread ) +{ + if ( !hThread ) + { + hThread = ThreadGetCurrentHandle(); + } + +#ifdef _WIN32 + return ::GetThreadPriority( (HANDLE)hThread ); +#else + struct sched_param thread_param; + int policy; + pthread_getschedparam( (pthread_t)hThread, &policy, &thread_param ); + return thread_param.sched_priority; +#endif +} + +//----------------------------------------------------------------------------- + +bool ThreadSetPriority( ThreadHandle_t hThread, int priority ) +{ + if ( !hThread ) + { + hThread = ThreadGetCurrentHandle(); + } + +#ifdef _WIN32 + return ( SetThreadPriority(hThread, priority) != 0 ); +#elif defined(POSIX) + struct sched_param thread_param; + thread_param.sched_priority = priority; + pthread_setschedparam( (pthread_t)hThread, SCHED_OTHER, &thread_param ); + return true; +#endif +} + +//----------------------------------------------------------------------------- + +void ThreadSetAffinity( ThreadHandle_t hThread, int nAffinityMask ) +{ + if ( !hThread ) + { + hThread = ThreadGetCurrentHandle(); + } + +#ifdef _WIN32 + SetThreadAffinityMask( hThread, nAffinityMask ); +#elif defined(POSIX) +// cpu_set_t cpuSet; +// CPU_ZERO( cpuSet ); +// for( int i = 0 ; i < 32; i++ ) +// if ( nAffinityMask & ( 1 << i ) ) +// CPU_SET( cpuSet, i ); +// sched_setaffinity( hThread, sizeof( cpuSet ), &cpuSet ); +#endif + +} + +//----------------------------------------------------------------------------- + +uint InitMainThread() +{ +#ifndef LINUX + // Skip doing the setname on Linux for the main thread. Here is why... + + // From Pierre-Loup e-mail about why pthread_setname_np() on the main thread + // in Linux will cause some tools to display "MainThrd" as the executable name: + // + // You have two things in procfs, comm and cmdline. Each of the threads have + // a different `comm`, which is the value you set through pthread_setname_np + // or prctl(PR_SET_NAME). Top can either display cmdline or comm; it + // switched to display comm by default; htop still displays cmdline by + // default. Top -c will output cmdline rather than comm. + // + // If you press 'H' while top is running it will display each thread as a + // separate process, so you will have different entries for MainThrd, + // MatQueue0, etc with their own CPU usage. But when that mode isn't enabled + // it just displays the 'comm' name from the first thread. + ThreadSetDebugName( "MainThrd" ); +#endif + +#ifdef _WIN32 + return ThreadGetCurrentId(); +#elif defined(POSIX) + return (uint)pthread_self(); +#endif +} + +uint g_ThreadMainThreadID = InitMainThread(); + +bool ThreadInMainThread() +{ + return ( ThreadGetCurrentId() == g_ThreadMainThreadID ); +} + +//----------------------------------------------------------------------------- +void DeclareCurrentThreadIsMainThread() +{ + g_ThreadMainThreadID = ThreadGetCurrentId(); +} + +bool ThreadJoin( ThreadHandle_t hThread, unsigned timeout ) +{ + // You should really never be calling this with a NULL thread handle. If you + // are then that probably implies a race condition or threading misunderstanding. + Assert( hThread ); + if ( !hThread ) + { + return false; + } + +#ifdef _WIN32 + DWORD dwWait = VCRHook_WaitForSingleObject((HANDLE)hThread, timeout); + if ( dwWait == WAIT_TIMEOUT) + return false; + if ( dwWait != WAIT_OBJECT_0 && ( dwWait != WAIT_FAILED && GetLastError() != 0 ) ) + { + Assert( 0 ); + return false; + } +#elif defined(POSIX) + if ( pthread_join( (pthread_t)hThread, NULL ) != 0 ) + return false; +#endif + return true; +} + +#ifdef RAD_TELEMETRY_ENABLED +void TelemetryThreadSetDebugName( ThreadId_t id, const char *pszName ); +#endif + +//----------------------------------------------------------------------------- + +void ThreadSetDebugName( ThreadId_t id, const char *pszName ) +{ + if( !pszName ) + return; + +#ifdef RAD_TELEMETRY_ENABLED + TelemetryThreadSetDebugName( id, pszName ); +#endif + +#ifdef _WIN32 + if ( Plat_IsInDebugSession() ) + { +#define MS_VC_EXCEPTION 0x406d1388 + + typedef struct tagTHREADNAME_INFO + { + DWORD dwType; // must be 0x1000 + LPCSTR szName; // pointer to name (in same addr space) + DWORD dwThreadID; // thread ID (-1 caller thread) + DWORD dwFlags; // reserved for future use, most be zero + } THREADNAME_INFO; + + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = pszName; + info.dwThreadID = id; + info.dwFlags = 0; + + __try + { + RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info); + } + __except (EXCEPTION_CONTINUE_EXECUTION) + { + } + } +#elif defined( _LINUX ) + // As of glibc v2.12, we can use pthread_setname_np. + typedef int (pthread_setname_np_func)(pthread_t, const char *); + static pthread_setname_np_func *s_pthread_setname_np_func = (pthread_setname_np_func *)dlsym(RTLD_DEFAULT, "pthread_setname_np"); + + if ( s_pthread_setname_np_func ) + { + if ( id == (uint32)-1 ) + id = pthread_self(); + + /* + pthread_setname_np() in phthread_setname.c has the following code: + + #define TASK_COMM_LEN 16 + size_t name_len = strlen (name); + if (name_len >= TASK_COMM_LEN) + return ERANGE; + + So we need to truncate the threadname to 16 or the call will just fail. + */ + char szThreadName[ 16 ]; + strncpy( szThreadName, pszName, ARRAYSIZE( szThreadName ) ); + szThreadName[ ARRAYSIZE( szThreadName ) - 1 ] = 0; + (*s_pthread_setname_np_func)( id, szThreadName ); + } +#endif +} + + +//----------------------------------------------------------------------------- + +#ifdef _WIN32 +ASSERT_INVARIANT( TW_FAILED == WAIT_FAILED ); +ASSERT_INVARIANT( TW_TIMEOUT == WAIT_TIMEOUT ); +ASSERT_INVARIANT( WAIT_OBJECT_0 == 0 ); + +int ThreadWaitForObjects( int nEvents, const HANDLE *pHandles, bool bWaitAll, unsigned timeout ) +{ + return VCRHook_WaitForMultipleObjects( nEvents, pHandles, bWaitAll, timeout ); +} +#endif + + +//----------------------------------------------------------------------------- +// Used to thread LoadLibrary on the 360 +//----------------------------------------------------------------------------- +static ThreadedLoadLibraryFunc_t s_ThreadedLoadLibraryFunc = 0; +PLATFORM_INTERFACE void SetThreadedLoadLibraryFunc( ThreadedLoadLibraryFunc_t func ) +{ + s_ThreadedLoadLibraryFunc = func; +} + +PLATFORM_INTERFACE ThreadedLoadLibraryFunc_t GetThreadedLoadLibraryFunc() +{ + return s_ThreadedLoadLibraryFunc; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +CThreadSyncObject::CThreadSyncObject() +#ifdef _WIN32 + : m_hSyncObject( NULL ), m_bCreatedHandle(false) +#elif defined(POSIX) + : m_bInitalized( false ) +#endif +{ +} + +//--------------------------------------------------------- + +CThreadSyncObject::~CThreadSyncObject() +{ +#ifdef _WIN32 + if ( m_hSyncObject && m_bCreatedHandle ) + { + if ( !CloseHandle(m_hSyncObject) ) + { + Assert( 0 ); + } + } +#elif defined(POSIX) + if ( m_bInitalized ) + { + pthread_cond_destroy( &m_Condition ); + pthread_mutex_destroy( &m_Mutex ); + m_bInitalized = false; + } +#endif +} + +//--------------------------------------------------------- + +bool CThreadSyncObject::operator!() const +{ +#ifdef _WIN32 + return !m_hSyncObject; +#elif defined(POSIX) + return !m_bInitalized; +#endif +} + +//--------------------------------------------------------- + +void CThreadSyncObject::AssertUseable() +{ +#ifdef THREADS_DEBUG +#ifdef _WIN32 + AssertMsg( m_hSyncObject, "Thread synchronization object is unuseable" ); +#elif defined(POSIX) + AssertMsg( m_bInitalized, "Thread synchronization object is unuseable" ); +#endif +#endif +} + +//--------------------------------------------------------- + +bool CThreadSyncObject::Wait( uint32 dwTimeout ) +{ +#ifdef THREADS_DEBUG + AssertUseable(); +#endif +#ifdef _WIN32 + return ( VCRHook_WaitForSingleObject( m_hSyncObject, dwTimeout ) == WAIT_OBJECT_0 ); +#elif defined(POSIX) + pthread_mutex_lock( &m_Mutex ); + bool bRet = false; + if ( m_cSet > 0 ) + { + bRet = true; + m_bWakeForEvent = false; + } + else + { + volatile int ret = 0; + + while ( !m_bWakeForEvent && ret != ETIMEDOUT ) + { + struct timeval tv; + gettimeofday( &tv, NULL ); + volatile struct timespec tm; + + uint64 actualTimeout = dwTimeout; + + if ( dwTimeout == TT_INFINITE && m_bManualReset ) + actualTimeout = 10; // just wait 10 msec at most for manual reset events and loop instead + + volatile uint64 nNanoSec = (uint64)tv.tv_usec*1000 + (uint64)actualTimeout*1000000; + tm.tv_sec = tv.tv_sec + nNanoSec /1000000000; + tm.tv_nsec = nNanoSec % 1000000000; + + do + { + ret = pthread_cond_timedwait( &m_Condition, &m_Mutex, (const timespec *)&tm ); + } + while( ret == EINTR ); + + bRet = ( ret == 0 ); + + if ( m_bManualReset ) + { + if ( m_cSet ) + break; + if ( dwTimeout == TT_INFINITE && ret == ETIMEDOUT ) + ret = 0; // force the loop to spin back around + } + } + + if ( bRet ) + m_bWakeForEvent = false; + } + if ( !m_bManualReset && bRet ) + m_cSet = 0; + pthread_mutex_unlock( &m_Mutex ); + return bRet; +#endif +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +CThreadEvent::CThreadEvent( bool bManualReset ) +{ +#ifdef _WIN32 + m_hSyncObject = CreateEvent( NULL, bManualReset, FALSE, NULL ); + m_bCreatedHandle = true; + AssertMsg1(m_hSyncObject, "Failed to create event (error 0x%x)", GetLastError() ); +#elif defined( POSIX ) + pthread_mutexattr_t Attr; + pthread_mutexattr_init( &Attr ); + pthread_mutex_init( &m_Mutex, &Attr ); + pthread_mutexattr_destroy( &Attr ); + pthread_cond_init( &m_Condition, NULL ); + m_bInitalized = true; + m_cSet = 0; + m_bWakeForEvent = false; + m_bManualReset = bManualReset; +#else +#error "Implement me" +#endif +} + +#ifdef _WIN32 +CThreadEvent::CThreadEvent( HANDLE hHandle ) +{ + m_hSyncObject = hHandle; + m_bCreatedHandle = false; + AssertMsg(m_hSyncObject, "Null event passed into constructor" ); +} +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + + +//--------------------------------------------------------- + +bool CThreadEvent::Set() +{ + AssertUseable(); +#ifdef _WIN32 + return ( SetEvent( m_hSyncObject ) != 0 ); +#elif defined(POSIX) + pthread_mutex_lock( &m_Mutex ); + m_cSet = 1; + m_bWakeForEvent = true; + int ret = pthread_cond_signal( &m_Condition ); + pthread_mutex_unlock( &m_Mutex ); + return ret == 0; +#endif +} + +//--------------------------------------------------------- + +bool CThreadEvent::Reset() +{ +#ifdef THREADS_DEBUG + AssertUseable(); +#endif +#ifdef _WIN32 + return ( ResetEvent( m_hSyncObject ) != 0 ); +#elif defined(POSIX) + pthread_mutex_lock( &m_Mutex ); + m_cSet = 0; + m_bWakeForEvent = false; + pthread_mutex_unlock( &m_Mutex ); + return true; +#endif +} + +//--------------------------------------------------------- + +bool CThreadEvent::Check() +{ +#ifdef THREADS_DEBUG + AssertUseable(); +#endif + return Wait( 0 ); +} + + + +bool CThreadEvent::Wait( uint32 dwTimeout ) +{ + return CThreadSyncObject::Wait( dwTimeout ); +} + +#ifdef _WIN32 +//----------------------------------------------------------------------------- +// +// CThreadSemaphore +// +// To get Posix implementation, try http://www-128.ibm.com/developerworks/eserver/library/es-win32linux-sem.html +// +//----------------------------------------------------------------------------- + +CThreadSemaphore::CThreadSemaphore( long initialValue, long maxValue ) +{ + if ( maxValue ) + { + AssertMsg( maxValue > 0, "Invalid max value for semaphore" ); + AssertMsg( initialValue >= 0 && initialValue <= maxValue, "Invalid initial value for semaphore" ); + + m_hSyncObject = CreateSemaphore( NULL, initialValue, maxValue, NULL ); + + AssertMsg1(m_hSyncObject, "Failed to create semaphore (error 0x%x)", GetLastError()); + } + else + { + m_hSyncObject = NULL; + } +} + +//--------------------------------------------------------- + +bool CThreadSemaphore::Release( long releaseCount, long *pPreviousCount ) +{ +#ifdef THRDTOOL_DEBUG + AssertUseable(); +#endif + return ( ReleaseSemaphore( m_hSyncObject, releaseCount, pPreviousCount ) != 0 ); +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +CThreadFullMutex::CThreadFullMutex( bool bEstablishInitialOwnership, const char *pszName ) +{ + m_hSyncObject = CreateMutex( NULL, bEstablishInitialOwnership, pszName ); + + AssertMsg1( m_hSyncObject, "Failed to create mutex (error 0x%x)", GetLastError() ); +} + +//--------------------------------------------------------- + +bool CThreadFullMutex::Release() +{ +#ifdef THRDTOOL_DEBUG + AssertUseable(); +#endif + return ( ReleaseMutex( m_hSyncObject ) != 0 ); +} + +#endif + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- + +CThreadLocalBase::CThreadLocalBase() +{ +#ifdef _WIN32 + m_index = TlsAlloc(); + AssertMsg( m_index != 0xFFFFFFFF, "Bad thread local" ); + if ( m_index == 0xFFFFFFFF ) + Error( "Out of thread local storage!\n" ); +#elif defined(POSIX) + if ( pthread_key_create( &m_index, NULL ) != 0 ) + Error( "Out of thread local storage!\n" ); +#endif +} + +//--------------------------------------------------------- + +CThreadLocalBase::~CThreadLocalBase() +{ +#ifdef _WIN32 + if ( m_index != 0xFFFFFFFF ) + TlsFree( m_index ); + m_index = 0xFFFFFFFF; +#elif defined(POSIX) + pthread_key_delete( m_index ); +#endif +} + +//--------------------------------------------------------- + +void * CThreadLocalBase::Get() const +{ +#ifdef _WIN32 + if ( m_index != 0xFFFFFFFF ) + return TlsGetValue( m_index ); + AssertMsg( 0, "Bad thread local" ); + return NULL; +#elif defined(POSIX) + void *value = pthread_getspecific( m_index ); + return value; +#endif +} + +//--------------------------------------------------------- + +void CThreadLocalBase::Set( void *value ) +{ +#ifdef _WIN32 + if (m_index != 0xFFFFFFFF) + TlsSetValue(m_index, value); + else + AssertMsg( 0, "Bad thread local" ); +#elif defined(POSIX) + if ( pthread_setspecific( m_index, value ) != 0 ) + AssertMsg( 0, "Bad thread local" ); +#endif +} + +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- + +#ifdef _WIN32 +#ifdef _X360 +#define TO_INTERLOCK_PARAM(p) ((long *)p) +#define TO_INTERLOCK_PTR_PARAM(p) ((void **)p) +#else +#define TO_INTERLOCK_PARAM(p) (p) +#define TO_INTERLOCK_PTR_PARAM(p) (p) +#endif + +#ifndef USE_INTRINSIC_INTERLOCKED +long ThreadInterlockedIncrement( long volatile *pDest ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedIncrement( TO_INTERLOCK_PARAM(pDest) ); +} + +long ThreadInterlockedDecrement( long volatile *pDest ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedDecrement( TO_INTERLOCK_PARAM(pDest) ); +} + +long ThreadInterlockedExchange( long volatile *pDest, long value ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedExchange( TO_INTERLOCK_PARAM(pDest), value ); +} + +long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedExchangeAdd( TO_INTERLOCK_PARAM(pDest), value ); +} + +long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ); +} + +bool ThreadInterlockedAssignIf( long volatile *pDest, long value, long comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + +#if !(defined(_WIN64) || defined (_X360)) + __asm + { + mov eax,comperand + mov ecx,pDest + mov edx,value + lock cmpxchg [ecx],edx + mov eax,0 + setz al + } +#else + return ( InterlockedCompareExchange( TO_INTERLOCK_PARAM(pDest), value, comperand ) == comperand ); +#endif +} + +#endif + +#if !defined( USE_INTRINSIC_INTERLOCKED ) || defined( _WIN64 ) +void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedExchangePointer( TO_INTERLOCK_PARAM(pDest), value ); +} + +void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ); +} + +bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); +#if !(defined(_WIN64) || defined (_X360)) + __asm + { + mov eax,comperand + mov ecx,pDest + mov edx,value + lock cmpxchg [ecx],edx + mov eax,0 + setz al + } +#else + return ( InterlockedCompareExchangePointer( TO_INTERLOCK_PTR_PARAM(pDest), value, comperand ) == comperand ); +#endif +} +#endif + +int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + +#if defined(_WIN64) || defined (_X360) + return InterlockedCompareExchange64( pDest, value, comperand ); +#else + __asm + { + lea esi,comperand; + lea edi,value; + + mov eax,[esi]; + mov edx,4[esi]; + mov ebx,[edi]; + mov ecx,4[edi]; + mov esi,pDest; + lock CMPXCHG8B [esi]; + } +#endif +} + +bool ThreadInterlockedAssignIf64(volatile int64 *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + +#if defined(PLATFORM_WINDOWS_PC32 ) + __asm + { + lea esi,comperand; + lea edi,value; + + mov eax,[esi]; + mov edx,4[esi]; + mov ebx,[edi]; + mov ecx,4[edi]; + mov esi,pDest; + lock CMPXCHG8B [esi]; + mov eax,0; + setz al; + } +#else + return ( ThreadInterlockedCompareExchange64( pDest, value, comperand ) == comperand ); +#endif +} + +#if defined( PLATFORM_64BITS ) + +#if _MSC_VER < 1500 +// This intrinsic isn't supported on VS2005. +extern "C" unsigned char _InterlockedCompareExchange128( int64 volatile * Destination, int64 ExchangeHigh, int64 ExchangeLow, int64 * ComparandResult ); +#endif + +bool ThreadInterlockedAssignIf128( volatile int128 *pDest, const int128 &value, const int128 &comperand ) +{ + Assert( ( (size_t)pDest % 16 ) == 0 ); + + volatile int64 *pDest64 = ( volatile int64 * )pDest; + int64 *pValue64 = ( int64 * )&value; + int64 *pComperand64 = ( int64 * )&comperand; + + // Description: + // The CMPXCHG16B instruction compares the 128-bit value in the RDX:RAX and RCX:RBX registers + // with a 128-bit memory location. If the values are equal, the zero flag (ZF) is set, + // and the RCX:RBX value is copied to the memory location. + // Otherwise, the ZF flag is cleared, and the memory value is copied to RDX:RAX. + + // _InterlockedCompareExchange128: http://msdn.microsoft.com/en-us/library/bb514094.aspx + return _InterlockedCompareExchange128( pDest64, pValue64[1], pValue64[0], pComperand64 ) == 1; +} + +#endif // PLATFORM_64BITS + +int64 ThreadInterlockedIncrement64( int64 volatile *pDest ) +{ + Assert( (size_t)pDest % 8 == 0 ); + + int64 Old; + + do + { + Old = *pDest; + } while (ThreadInterlockedCompareExchange64(pDest, Old + 1, Old) != Old); + + return Old + 1; +} + +int64 ThreadInterlockedDecrement64( int64 volatile *pDest ) +{ + Assert( (size_t)pDest % 8 == 0 ); + int64 Old; + + do + { + Old = *pDest; + } while (ThreadInterlockedCompareExchange64(pDest, Old - 1, Old) != Old); + + return Old - 1; +} + +int64 ThreadInterlockedExchange64( int64 volatile *pDest, int64 value ) +{ + Assert( (size_t)pDest % 8 == 0 ); + int64 Old; + + do + { + Old = *pDest; + } while (ThreadInterlockedCompareExchange64(pDest, value, Old) != Old); + + return Old; +} + +int64 ThreadInterlockedExchangeAdd64( int64 volatile *pDest, int64 value ) +{ + Assert( (size_t)pDest % 8 == 0 ); + int64 Old; + + do + { + Old = *pDest; + } while (ThreadInterlockedCompareExchange64(pDest, Old + value, Old) != Old); + + return Old; +} + +#elif defined(GNUC) + +#ifdef OSX +#include +#endif + + +long ThreadInterlockedIncrement( long volatile *pDest ) +{ + return __sync_fetch_and_add( pDest, 1 ) + 1; +} + +long ThreadInterlockedDecrement( long volatile *pDest ) +{ + return __sync_fetch_and_sub( pDest, 1 ) - 1; +} + +long ThreadInterlockedExchange( long volatile *pDest, long value ) +{ + return __sync_lock_test_and_set( pDest, value ); +} + +long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) +{ + return __sync_fetch_and_add( pDest, value ); +} + +long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) +{ + return __sync_val_compare_and_swap( pDest, comperand, value ); +} + +bool ThreadInterlockedAssignIf( long volatile *pDest, long value, long comperand ) +{ + return __sync_bool_compare_and_swap( pDest, comperand, value ); +} + +void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) +{ + return __sync_lock_test_and_set( pDest, value ); +} + +void *ThreadInterlockedCompareExchangePointer( void *volatile *pDest, void *value, void *comperand ) +{ + return __sync_val_compare_and_swap( pDest, comperand, value ); +} + +bool ThreadInterlockedAssignPointerIf( void * volatile *pDest, void *value, void *comperand ) +{ + return __sync_bool_compare_and_swap( pDest, comperand, value ); +} + +int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) +{ +#if defined(OSX) + int64 retVal = *pDest; + if ( OSAtomicCompareAndSwap64( comperand, value, pDest ) ) + retVal = *pDest; + + return retVal; +#else + return __sync_val_compare_and_swap( pDest, comperand, value ); +#endif +} + +bool ThreadInterlockedAssignIf64( int64 volatile * pDest, int64 value, int64 comperand ) +{ + return __sync_bool_compare_and_swap( pDest, comperand, value ); +} + +int64 ThreadInterlockedExchange64( int64 volatile *pDest, int64 value ) +{ + Assert( (size_t)pDest % 8 == 0 ); + int64 Old; + + do + { + Old = *pDest; + } while (ThreadInterlockedCompareExchange64(pDest, value, Old) != Old); + + return Old; +} + + +#else +// This will perform horribly, +#error "Falling back to mutexed interlocked operations, you really don't have intrinsics you can use?"ß +CThreadMutex g_InterlockedMutex; + +long ThreadInterlockedIncrement( long volatile *pDest ) +{ + AUTO_LOCK( g_InterlockedMutex ); + return ++(*pDest); +} + +long ThreadInterlockedDecrement( long volatile *pDest ) +{ + AUTO_LOCK( g_InterlockedMutex ); + return --(*pDest); +} + +long ThreadInterlockedExchange( long volatile *pDest, long value ) +{ + AUTO_LOCK( g_InterlockedMutex ); + long retVal = *pDest; + *pDest = value; + return retVal; +} + +void *ThreadInterlockedExchangePointer( void * volatile *pDest, void *value ) +{ + AUTO_LOCK( g_InterlockedMutex ); + void *retVal = *pDest; + *pDest = value; + return retVal; +} + +long ThreadInterlockedExchangeAdd( long volatile *pDest, long value ) +{ + AUTO_LOCK( g_InterlockedMutex ); + long retVal = *pDest; + *pDest += value; + return retVal; +} + +long ThreadInterlockedCompareExchange( long volatile *pDest, long value, long comperand ) +{ + AUTO_LOCK( g_InterlockedMutex ); + long retVal = *pDest; + if ( *pDest == comperand ) + *pDest = value; + return retVal; +} + +void *ThreadInterlockedCompareExchangePointer( void * volatile *pDest, void *value, void *comperand ) +{ + AUTO_LOCK( g_InterlockedMutex ); + void *retVal = *pDest; + if ( *pDest == comperand ) + *pDest = value; + return retVal; +} + + +int64 ThreadInterlockedCompareExchange64( int64 volatile *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + AUTO_LOCK( g_InterlockedMutex ); + int64 retVal = *pDest; + if ( *pDest == comperand ) + *pDest = value; + return retVal; +} + +int64 ThreadInterlockedExchange64( int64 volatile *pDest, int64 value ) +{ + Assert( (size_t)pDest % 8 == 0 ); + int64 Old; + + do + { + Old = *pDest; + } while (ThreadInterlockedCompareExchange64(pDest, value, Old) != Old); + + return Old; +} + +bool ThreadInterlockedAssignIf64(volatile int64 *pDest, int64 value, int64 comperand ) +{ + Assert( (size_t)pDest % 8 == 0 ); + return ( ThreadInterlockedCompareExchange64( pDest, value, comperand ) == comperand ); +} + +bool ThreadInterlockedAssignIf( long volatile *pDest, long value, long comperand ) +{ + Assert( (size_t)pDest % 4 == 0 ); + return ( ThreadInterlockedCompareExchange( pDest, value, comperand ) == comperand ); +} + +#endif + +//----------------------------------------------------------------------------- + +#if defined(_WIN32) && defined(THREAD_PROFILER) +void ThreadNotifySyncNoop(void *p) {} + +#define MAP_THREAD_PROFILER_CALL( from, to ) \ + void from(void *p) \ + { \ + static CDynamicFunction dynFunc( "libittnotify.dll", #to, ThreadNotifySyncNoop ); \ + (*dynFunc)(p); \ + } + +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncPrepare, __itt_notify_sync_prepare ); +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncCancel, __itt_notify_sync_cancel ); +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncAcquired, __itt_notify_sync_acquired ); +MAP_THREAD_PROFILER_CALL( ThreadNotifySyncReleasing, __itt_notify_sync_releasing ); + +#endif + +//----------------------------------------------------------------------------- +// +// CThreadMutex +// +//----------------------------------------------------------------------------- + +#ifndef POSIX +CThreadMutex::CThreadMutex() +{ +#ifdef THREAD_MUTEX_TRACING_ENABLED + memset( &m_CriticalSection, 0, sizeof(m_CriticalSection) ); +#endif + InitializeCriticalSectionAndSpinCount((CRITICAL_SECTION *)&m_CriticalSection, 4000); +#ifdef THREAD_MUTEX_TRACING_SUPPORTED + // These need to be initialized unconditionally in case mixing release & debug object modules + // Lock and unlock may be emitted as COMDATs, in which case may get spurious output + m_currentOwnerID = m_lockCount = 0; + m_bTrace = false; +#endif +} + +CThreadMutex::~CThreadMutex() +{ + DeleteCriticalSection((CRITICAL_SECTION *)&m_CriticalSection); +} +#endif // !POSIX + +#if defined( _WIN32 ) && !defined( _X360 ) +typedef BOOL (WINAPI*TryEnterCriticalSectionFunc_t)(LPCRITICAL_SECTION); +static CDynamicFunction DynTryEnterCriticalSection( "Kernel32.dll", "TryEnterCriticalSection" ); +#elif defined( _X360 ) +#define DynTryEnterCriticalSection TryEnterCriticalSection +#endif + +bool CThreadMutex::TryLock() +{ + +#if defined( _WIN32 ) +#ifdef THREAD_MUTEX_TRACING_ENABLED + uint thisThreadID = ThreadGetCurrentId(); + if ( m_bTrace && m_currentOwnerID && ( m_currentOwnerID != thisThreadID ) ) + Msg( "Thread %u about to try-wait for lock %p owned by %u\n", ThreadGetCurrentId(), (CRITICAL_SECTION *)&m_CriticalSection, m_currentOwnerID ); +#endif + if ( DynTryEnterCriticalSection != NULL ) + { + if ( (*DynTryEnterCriticalSection )( (CRITICAL_SECTION *)&m_CriticalSection ) != FALSE ) + { +#ifdef THREAD_MUTEX_TRACING_ENABLED + if (m_lockCount == 0) + { + // we now own it for the first time. Set owner information + m_currentOwnerID = thisThreadID; + if ( m_bTrace ) + Msg( "Thread %u now owns lock 0x%p\n", m_currentOwnerID, (CRITICAL_SECTION *)&m_CriticalSection ); + } + m_lockCount++; +#endif + return true; + } + return false; + } + Lock(); + return true; +#elif defined( POSIX ) + return pthread_mutex_trylock( &m_Mutex ) == 0; +#else +#error "Implement me!" + return true; +#endif +} + +//----------------------------------------------------------------------------- +// +// CThreadFastMutex +// +//----------------------------------------------------------------------------- + +#define THREAD_SPIN (8*1024) + +void CThreadFastMutex::Lock( const uint32 threadId, unsigned nSpinSleepTime ) volatile +{ + int i; + if ( nSpinSleepTime != TT_INFINITE ) + { + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLock( threadId ) ) + { + return; + } + ThreadPause(); + } + + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLock( threadId ) ) + { + return; + } + ThreadPause(); + if ( i % 1024 == 0 ) + { + ThreadSleep( 0 ); + } + } + +#ifdef _WIN32 + if ( !nSpinSleepTime && GetThreadPriority( GetCurrentThread() ) > THREAD_PRIORITY_NORMAL ) + { + nSpinSleepTime = 1; + } + else +#endif + + if ( nSpinSleepTime ) + { + for ( i = THREAD_SPIN; i != 0; --i ) + { + if ( TryLock( threadId ) ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLock( threadId ) ) + { + return; + } + + ThreadPause(); + ThreadSleep( nSpinSleepTime ); + } + } + else + { + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLock( threadId ) ) + { + return; + } + + ThreadPause(); + } + } +} + +//----------------------------------------------------------------------------- +// +// CThreadRWLock +// +//----------------------------------------------------------------------------- + +void CThreadRWLock::WaitForRead() +{ + m_nPendingReaders++; + + do + { + m_mutex.Unlock(); + m_CanRead.Wait(); + m_mutex.Lock(); + } + while (m_nWriters); + + m_nPendingReaders--; +} + + +void CThreadRWLock::LockForWrite() +{ + m_mutex.Lock(); + bool bWait = ( m_nWriters != 0 || m_nActiveReaders != 0 ); + m_nWriters++; + m_CanRead.Reset(); + m_mutex.Unlock(); + + if ( bWait ) + { + m_CanWrite.Wait(); + } +} + +void CThreadRWLock::UnlockWrite() +{ + m_mutex.Lock(); + m_nWriters--; + if ( m_nWriters == 0) + { + if ( m_nPendingReaders ) + { + m_CanRead.Set(); + } + } + else + { + m_CanWrite.Set(); + } + m_mutex.Unlock(); +} + +//----------------------------------------------------------------------------- +// +// CThreadSpinRWLock +// +//----------------------------------------------------------------------------- + +void CThreadSpinRWLock::SpinLockForWrite( const uint32 threadId ) +{ + int i; + + for ( i = 1000; i != 0; --i ) + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + ThreadPause(); + } + + for ( i = 20000; i != 0; --i ) + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + + ThreadPause(); + ThreadSleep( 0 ); + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if ( TryLockForWrite( threadId ) ) + { + return; + } + + ThreadPause(); + ThreadSleep( 1 ); + } +} + +void CThreadSpinRWLock::LockForRead() +{ + int i; + + // In order to grab a read lock, the number of readers must not change and no thread can own the write lock + LockInfo_t oldValue; + LockInfo_t newValue; + + oldValue.m_nReaders = m_lockInfo.m_nReaders; + oldValue.m_writerId = 0; + newValue.m_nReaders = oldValue.m_nReaders + 1; + newValue.m_writerId = 0; + + if( m_nWriters == 0 && AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders + 1; + + for ( i = 1000; i != 0; --i ) + { + if( m_nWriters == 0 && AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders + 1; + } + + for ( i = 20000; i != 0; --i ) + { + if( m_nWriters == 0 && AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + ThreadSleep( 0 ); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders + 1; + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if( m_nWriters == 0 && AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + ThreadSleep( 1 ); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders + 1; + } +} + +void CThreadSpinRWLock::UnlockRead() +{ + int i; + + Assert( m_lockInfo.m_nReaders > 0 && m_lockInfo.m_writerId == 0 ); + LockInfo_t oldValue; + LockInfo_t newValue; + + oldValue.m_nReaders = m_lockInfo.m_nReaders; + oldValue.m_writerId = 0; + newValue.m_nReaders = oldValue.m_nReaders - 1; + newValue.m_writerId = 0; + + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + + for ( i = 500; i != 0; --i ) + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } + + for ( i = 20000; i != 0; --i ) + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + ThreadSleep( 0 ); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } + + for ( ;; ) // coded as for instead of while to make easy to breakpoint success + { + if( AssignIf( newValue, oldValue ) ) + return; + ThreadPause(); + ThreadSleep( 1 ); + oldValue.m_nReaders = m_lockInfo.m_nReaders; + newValue.m_nReaders = oldValue.m_nReaders - 1; + } +} + +void CThreadSpinRWLock::UnlockWrite() +{ + Assert( m_lockInfo.m_writerId == ThreadGetCurrentId() && m_lockInfo.m_nReaders == 0 ); + static const LockInfo_t newValue = { 0, 0 }; +#if defined(_X360) + // X360TBD: Serious Perf implications, not yet. __sync(); +#endif + ThreadInterlockedExchange64( (int64 *)&m_lockInfo, *((int64 *)&newValue) ); + m_nWriters--; +} + + + +//----------------------------------------------------------------------------- +// +// CThread +// +//----------------------------------------------------------------------------- + +CThreadLocalPtr g_pCurThread; + +//--------------------------------------------------------- + +CThread::CThread() +: +#ifdef _WIN32 + m_hThread( NULL ), +#endif + m_threadId( 0 ), + m_result( 0 ), + m_flags( 0 ) +{ + m_szName[0] = 0; +} + +//--------------------------------------------------------- + +CThread::~CThread() +{ +#ifdef _WIN32 + if (m_hThread) +#elif defined(POSIX) + if ( m_threadId ) +#endif + { + if ( IsAlive() ) + { + Msg( "Illegal termination of worker thread! Threads must negotiate an end to the thread before the CThread object is destroyed.\n" ); +#ifdef _WIN32 + + DoNewAssertDialog( __FILE__, __LINE__, "Illegal termination of worker thread! Threads must negotiate an end to the thread before the CThread object is destroyed.\n" ); +#endif + if ( GetCurrentCThread() == this ) + { + Stop(); // BUGBUG: Alfred - this doesn't make sense, this destructor fires from the hosting thread not the thread itself!! + } + } + +#ifdef _WIN32 + // Now that the worker thread has exited (which we know because we presumably waited + // on the thread handle for it to exit) we can finally close the thread handle. We + // cannot do this any earlier, and certainly not in CThread::ThreadProc(). + CloseHandle( m_hThread ); +#endif + } +} + + +//--------------------------------------------------------- + +const char *CThread::GetName() +{ + AUTO_LOCK( m_Lock ); + if ( !m_szName[0] ) + { +#ifdef _WIN32 + _snprintf( m_szName, sizeof(m_szName) - 1, "Thread(%p/%p)", this, m_hThread ); +#elif defined(POSIX) + _snprintf( m_szName, sizeof(m_szName) - 1, "Thread(0x%x/0x%x)", (uint)this, (uint)m_threadId ); +#endif + m_szName[sizeof(m_szName) - 1] = 0; + } + return m_szName; +} + +//--------------------------------------------------------- + +void CThread::SetName(const char *pszName) +{ + AUTO_LOCK( m_Lock ); + strncpy( m_szName, pszName, sizeof(m_szName) - 1 ); + m_szName[sizeof(m_szName) - 1] = 0; +} + +//--------------------------------------------------------- + +bool CThread::Start( unsigned nBytesStack ) +{ + AUTO_LOCK( m_Lock ); + + if ( IsAlive() ) + { + AssertMsg( 0, "Tried to create a thread that has already been created!" ); + return false; + } + + bool bInitSuccess = false; + CThreadEvent createComplete; + ThreadInit_t init = { this, &createComplete, &bInitSuccess }; + +#ifdef _WIN32 + HANDLE hThread; + m_hThread = hThread = (HANDLE)VCRHook_CreateThread( NULL, + nBytesStack, + (LPTHREAD_START_ROUTINE)GetThreadProc(), + new ThreadInit_t(init), + CREATE_SUSPENDED, + &m_threadId ); + if ( !hThread ) + { + AssertMsg1( 0, "Failed to create thread (error 0x%x)", GetLastError() ); + return false; + } + Plat_ApplyHardwareDataBreakpointsToNewThread( m_threadId ); + ResumeThread( hThread ); + +#elif defined(POSIX) + pthread_attr_t attr; + pthread_attr_init( &attr ); + // From http://www.kernel.org/doc/man-pages/online/pages/man3/pthread_attr_setstacksize.3.html + // A thread's stack size is fixed at the time of thread creation. Only the main thread can dynamically grow its stack. + pthread_attr_setstacksize( &attr, MAX( nBytesStack, 1024u*1024 ) ); + if ( pthread_create( &m_threadId, &attr, (void *(*)(void *))GetThreadProc(), new ThreadInit_t( init ) ) != 0 ) + { + AssertMsg1( 0, "Failed to create thread (error 0x%x)", GetLastError() ); + return false; + } + Plat_ApplyHardwareDataBreakpointsToNewThread( (long unsigned int)m_threadId ); + bInitSuccess = true; +#endif + +#if !defined( OSX ) + ThreadSetDebugName( m_threadId, m_szName ); +#endif + + if ( !WaitForCreateComplete( &createComplete ) ) + { + Msg( "Thread failed to initialize\n" ); +#ifdef _WIN32 + CloseHandle( m_hThread ); + m_hThread = NULL; + m_threadId = 0; +#elif defined(POSIX) + m_threadId = 0; +#endif + return false; + } + + if ( !bInitSuccess ) + { + Msg( "Thread failed to initialize\n" ); +#ifdef _WIN32 + CloseHandle( m_hThread ); + m_hThread = NULL; + m_threadId = 0; +#elif defined(POSIX) + m_threadId = 0; +#endif + return false; + } + +#ifdef _WIN32 + if ( !m_hThread ) + { + Msg( "Thread exited immediately\n" ); + } +#endif + +#ifdef _WIN32 + return !!m_hThread; +#elif defined(POSIX) + return !!m_threadId; +#endif +} + +//--------------------------------------------------------- +// +// Return true if the thread exists. false otherwise +// + +bool CThread::IsAlive() +{ +#ifdef _WIN32 + DWORD dwExitCode; + + return ( m_hThread && + GetExitCodeThread( m_hThread, &dwExitCode ) && + dwExitCode == STILL_ACTIVE ); +#elif defined(POSIX) + return m_threadId; +#endif +} + +//--------------------------------------------------------- + +bool CThread::Join(unsigned timeout) +{ +#ifdef _WIN32 + if ( m_hThread ) +#elif defined(POSIX) + if ( m_threadId ) +#endif + { + AssertMsg(GetCurrentCThread() != this, _T("Thread cannot be joined with self")); + +#ifdef _WIN32 + return ThreadJoin( (ThreadHandle_t)m_hThread ); +#elif defined(POSIX) + return ThreadJoin( (ThreadHandle_t)m_threadId ); +#endif + } + return true; +} + +//--------------------------------------------------------- + +#ifdef _WIN32 + +HANDLE CThread::GetThreadHandle() +{ + return m_hThread; +} + +#endif + +#if defined( _WIN32 ) || defined( LINUX ) + +//--------------------------------------------------------- + +uint CThread::GetThreadId() +{ + return m_threadId; +} + +#endif + +//--------------------------------------------------------- + +int CThread::GetResult() +{ + return m_result; +} + +//--------------------------------------------------------- +// +// Forcibly, abnormally, but relatively cleanly stop the thread +// + +void CThread::Stop(int exitCode) +{ + if ( !IsAlive() ) + return; + + if ( GetCurrentCThread() == this ) + { + m_result = exitCode; + if ( !( m_flags & SUPPORT_STOP_PROTOCOL ) ) + { + OnExit(); + g_pCurThread = (int)NULL; + +#ifdef _WIN32 + CloseHandle( m_hThread ); + m_hThread = NULL; +#endif + Cleanup(); + } + throw exitCode; + } + else + AssertMsg( 0, "Only thread can stop self: Use a higher-level protocol"); +} + +//--------------------------------------------------------- + +int CThread::GetPriority() const +{ +#ifdef _WIN32 + return GetThreadPriority(m_hThread); +#elif defined(POSIX) + struct sched_param thread_param; + int policy; + pthread_getschedparam( m_threadId, &policy, &thread_param ); + return thread_param.sched_priority; +#endif +} + +//--------------------------------------------------------- + +bool CThread::SetPriority(int priority) +{ +#ifdef _WIN32 + return ThreadSetPriority( (ThreadHandle_t)m_hThread, priority ); +#else + return ThreadSetPriority( (ThreadHandle_t)m_threadId, priority ); +#endif +} + + +//--------------------------------------------------------- + +void CThread::SuspendCooperative() +{ + if ( ThreadGetCurrentId() == (ThreadId_t)m_threadId ) + { + m_SuspendEventSignal.Set(); + m_nSuspendCount = 1; + m_SuspendEvent.Wait(); + m_nSuspendCount = 0; + } + else + { + Assert( !"Suspend not called from worker thread, this would be a bug" ); + } +} + +//--------------------------------------------------------- + +void CThread::ResumeCooperative() +{ + Assert( m_nSuspendCount == 1 ); + m_SuspendEvent.Set(); +} + + +void CThread::BWaitForThreadSuspendCooperative() +{ + m_SuspendEventSignal.Wait(); +} + + +#ifndef LINUX +//--------------------------------------------------------- + +unsigned int CThread::Suspend() +{ +#ifdef _WIN32 + return ( SuspendThread(m_hThread) != 0 ); +#elif defined(OSX) + int susCount = m_nSuspendCount++; + while ( thread_suspend( pthread_mach_thread_np(m_threadId) ) != KERN_SUCCESS ) + { + }; + return ( susCount) != 0; +#else +#error +#endif +} + +//--------------------------------------------------------- + +unsigned int CThread::Resume() +{ +#ifdef _WIN32 + return ( ResumeThread(m_hThread) != 0 ); +#elif defined(OSX) + int susCount = m_nSuspendCount++; + while ( thread_resume( pthread_mach_thread_np(m_threadId) ) != KERN_SUCCESS ) + { + }; + return ( susCount - 1) != 0; +#else +#error +#endif +} +#endif + + +//--------------------------------------------------------- + +bool CThread::Terminate(int exitCode) +{ +#ifndef _X360 +#ifdef _WIN32 + // I hope you know what you're doing! + if (!TerminateThread(m_hThread, exitCode)) + return false; + CloseHandle( m_hThread ); + m_hThread = NULL; + Cleanup(); +#elif defined(POSIX) + pthread_kill( m_threadId, SIGKILL ); + Cleanup(); +#endif + + return true; +#else + AssertMsg( 0, "Cannot terminate a thread on the Xbox!" ); + return false; +#endif +} + +//--------------------------------------------------------- +// +// Get the Thread object that represents the current thread, if any. +// Can return NULL if the current thread was not created using +// CThread +// + +CThread *CThread::GetCurrentCThread() +{ + return g_pCurThread; +} + +//--------------------------------------------------------- +// +// Offer a context switch. Under Win32, equivalent to Sleep(0) +// + +void CThread::Yield() +{ +#ifdef _WIN32 + ::Sleep(0); +#elif defined(POSIX) + pthread_yield(); +#endif +} + +//--------------------------------------------------------- +// +// This method causes the current thread to yield and not to be +// scheduled for further execution until a certain amount of real +// time has elapsed, more or less. +// + +void CThread::Sleep(unsigned duration) +{ +#ifdef _WIN32 + ::Sleep(duration); +#elif defined(POSIX) + usleep( duration * 1000 ); +#endif +} + +//--------------------------------------------------------- + +bool CThread::Init() +{ + return true; +} + +//--------------------------------------------------------- + +void CThread::OnExit() +{ +} + +//--------------------------------------------------------- + +void CThread::Cleanup() +{ + m_threadId = 0; +} + +//--------------------------------------------------------- +bool CThread::WaitForCreateComplete(CThreadEvent * pEvent) +{ + // Force serialized thread creation... + if (!pEvent->Wait(60000)) + { + AssertMsg( 0, "Probably deadlock or failure waiting for thread to initialize." ); + return false; + } + return true; +} + +//--------------------------------------------------------- + +bool CThread::IsThreadRunning() +{ +#ifdef _PS3 + // ThreadIsThreadIdRunning() doesn't work on PS3 if the thread is in a zombie state + return m_eventTheadExit.Check(); +#else + return ThreadIsThreadIdRunning( (ThreadId_t)m_threadId ); +#endif +} + +//--------------------------------------------------------- + +CThread::ThreadProc_t CThread::GetThreadProc() +{ + return ThreadProc; +} + +//--------------------------------------------------------- + +unsigned __stdcall CThread::ThreadProc(LPVOID pv) +{ + std::auto_ptr pInit((ThreadInit_t *)pv); + +#ifdef _X360 + // Make sure all threads are consistent w.r.t floating-point math + SetupFPUControlWord(); +#endif + + CThread *pThread = pInit->pThread; + g_pCurThread = pThread; + + g_pCurThread->m_pStackBase = AlignValue( &pThread, 4096 ); + + pInit->pThread->m_result = -1; + + bool bInitSuccess = true; + if ( pInit->pfInitSuccess ) + *(pInit->pfInitSuccess) = false; + + try + { + bInitSuccess = pInit->pThread->Init(); + } + + catch (...) + { + pInit->pInitCompleteEvent->Set(); + throw; + } + + if ( pInit->pfInitSuccess ) + *(pInit->pfInitSuccess) = bInitSuccess; + pInit->pInitCompleteEvent->Set(); + if (!bInitSuccess) + return 0; + + if ( pInit->pThread->m_flags & SUPPORT_STOP_PROTOCOL ) + { + try + { + pInit->pThread->m_result = pInit->pThread->Run(); + } + + catch (...) + { + } + } + else + { + pInit->pThread->m_result = pInit->pThread->Run(); + } + + pInit->pThread->OnExit(); + g_pCurThread = (int)NULL; + pInit->pThread->Cleanup(); + + return pInit->pThread->m_result; +} + + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +CWorkerThread::CWorkerThread() +: m_EventSend(true), // must be manual-reset for PeekCall() + m_EventComplete(true), // must be manual-reset to handle multiple wait with thread properly + m_Param(0), + m_pParamFunctor(NULL), + m_ReturnVal(0) +{ +} + +//--------------------------------------------------------- + +int CWorkerThread::CallWorker(unsigned dw, unsigned timeout, bool fBoostWorkerPriorityToMaster, CFunctor *pParamFunctor) +{ + return Call(dw, timeout, fBoostWorkerPriorityToMaster, NULL, pParamFunctor); +} + +//--------------------------------------------------------- + +int CWorkerThread::CallMaster(unsigned dw, unsigned timeout) +{ + return Call(dw, timeout, false); +} + +//--------------------------------------------------------- + +CThreadEvent &CWorkerThread::GetCallHandle() +{ + return m_EventSend; +} + +//--------------------------------------------------------- + +unsigned CWorkerThread::GetCallParam( CFunctor **ppParamFunctor ) const +{ + if( ppParamFunctor ) + *ppParamFunctor = m_pParamFunctor; + return m_Param; +} + +//--------------------------------------------------------- + +int CWorkerThread::BoostPriority() +{ + int iInitialPriority = GetPriority(); + const int iNewPriority = ThreadGetPriority( (ThreadHandle_t)GetThreadID() ); + if (iNewPriority > iInitialPriority) + ThreadSetPriority( (ThreadHandle_t)GetThreadID(), iNewPriority); + return iInitialPriority; +} + +//--------------------------------------------------------- + +static uint32 __stdcall DefaultWaitFunc( int nEvents, CThreadEvent * const *pEvents, int bWaitAll, uint32 timeout ) +{ + return ThreadWaitForEvents( nEvents, pEvents, bWaitAll!=0, timeout ); +// return VCRHook_WaitForMultipleObjects( nHandles, (const void **)pHandles, bWaitAll, timeout ); +} + + +int CWorkerThread::Call(unsigned dwParam, unsigned timeout, bool fBoostPriority, WaitFunc_t pfnWait, CFunctor *pParamFunctor) +{ + AssertMsg(!m_EventSend.Check(), "Cannot perform call if there's an existing call pending" ); + + AUTO_LOCK( m_Lock ); + + if (!IsAlive()) + return WTCR_FAIL; + + int iInitialPriority = 0; + if (fBoostPriority) + { + iInitialPriority = BoostPriority(); + } + + // set the parameter, signal the worker thread, wait for the completion to be signaled + m_Param = dwParam; + m_pParamFunctor = pParamFunctor; + + m_EventComplete.Reset(); + m_EventSend.Set(); + + WaitForReply( timeout, pfnWait ); + + // MWD: Investigate why setting thread priorities is killing the 360 +#ifndef _X360 + if (fBoostPriority) + SetPriority(iInitialPriority); +#endif + + return m_ReturnVal; +} + +//--------------------------------------------------------- +// +// Wait for a request from the client +// +//--------------------------------------------------------- +int CWorkerThread::WaitForReply( unsigned timeout ) +{ + return WaitForReply( timeout, NULL ); +} + +int CWorkerThread::WaitForReply( unsigned timeout, WaitFunc_t pfnWait ) +{ + if (!pfnWait) + { + pfnWait = DefaultWaitFunc; + } + +#ifdef WIN32 + CThreadEvent threadEvent( GetThreadHandle() ); +#endif + + CThreadEvent *waits[] = + { +#ifdef WIN32 + &threadEvent, +#endif + &m_EventComplete + }; + + + unsigned result; + bool bInDebugger = Plat_IsInDebugSession(); + + do + { +#ifdef WIN32 + // Make sure the thread handle hasn't been closed + if ( !GetThreadHandle() ) + { + result = WAIT_OBJECT_0 + 1; + break; + } +#endif + result = (*pfnWait)((sizeof(waits) / sizeof(waits[0])), waits, false, + (timeout != TT_INFINITE) ? timeout : 30000); + + AssertMsg(timeout != TT_INFINITE || result != WAIT_TIMEOUT, "Possible hung thread, call to thread timed out"); + + } while ( bInDebugger && ( timeout == TT_INFINITE && result == WAIT_TIMEOUT ) ); + + if ( result != WAIT_OBJECT_0 + 1 ) + { + if (result == WAIT_TIMEOUT) + m_ReturnVal = WTCR_TIMEOUT; + else if (result == WAIT_OBJECT_0) + { + DevMsg( 2, "Thread failed to respond, probably exited\n"); + m_EventSend.Reset(); + m_ReturnVal = WTCR_TIMEOUT; + } + else + { + m_EventSend.Reset(); + m_ReturnVal = WTCR_THREAD_GONE; + } + } + + return m_ReturnVal; +} + + +//--------------------------------------------------------- +// +// Wait for a request from the client +// +//--------------------------------------------------------- + +bool CWorkerThread::WaitForCall(unsigned * pResult) +{ + return WaitForCall(TT_INFINITE, pResult); +} + +//--------------------------------------------------------- + +bool CWorkerThread::WaitForCall(unsigned dwTimeout, unsigned * pResult) +{ + bool returnVal = m_EventSend.Wait(dwTimeout); + if (pResult) + *pResult = m_Param; + return returnVal; +} + +//--------------------------------------------------------- +// +// is there a request? +// + +bool CWorkerThread::PeekCall(unsigned * pParam, CFunctor **ppParamFunctor) +{ + if (!m_EventSend.Check()) + { + return false; + } + else + { + if (pParam) + { + *pParam = m_Param; + } + if( ppParamFunctor ) + { + *ppParamFunctor = m_pParamFunctor; + } + return true; + } +} + +//--------------------------------------------------------- +// +// Reply to the request +// + +void CWorkerThread::Reply(unsigned dw) +{ + m_Param = 0; + m_ReturnVal = dw; + + // The request is now complete so PeekCall() should fail from + // now on + // + // This event should be reset BEFORE we signal the client + m_EventSend.Reset(); + + // Tell the client we're finished + m_EventComplete.Set(); +} + +//----------------------------------------------------------------------------- diff --git a/tier0/tier0.vpc b/tier0/tier0.vpc new file mode 100644 index 0000000..dbb9759 --- /dev/null +++ b/tier0/tier0.vpc @@ -0,0 +1,267 @@ +//----------------------------------------------------------------------------- +// TIER0.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$MacroRequired "PLATSUBDIR" + +$Macro SRCDIR ".." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $General + { + // X360 version publishes to some other directory then copies here so we need to tell VPC to track this + // or else it won't know what depends on this project. + $AdditionalOutputFiles "$SRCDIR\lib\public\$(TargetName).lib" [$X360] + } + + $Compiler + { + $PreprocessorDefinitions "$BASE;TIER0_DLL_EXPORT" + $Create/UsePrecompiledHeader "Use Precompiled Header (/Yu)" + $Create/UsePCHThroughFile "pch_tier0.h" + $PrecompiledHeaderFile "$(IntDir)/tier0.pch" + } + + $Compiler [$WINDOWS] + { + $AdditionalIncludeDirectories "$BASE;..\public\WindowsSDK" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib" [$WINDOWS] + + // pc publishes the import library directly + $ImportLibrary "$LIBPUBLIC\$(TargetName).lib" [$WINDOWS] + + // 360 publishes the import library via a post build step + $ImportLibrary "$(TargetDir)\$(TargetName).lib" [$X360] + + $ImportLibrary "$LIBPUBLIC\$_IMPLIB_PREFIX$OUTBINNAME$_IMPLIB_EXT" [$POSIX] + + + + // 360 will auto generate a def file for this import library + $ModuleDefinitionFile " " [$X360] + $AdditionalOptions "$BASE /AUTODEF:xbox\xbox.def" [$X360] + $SystemLibraries "rt" [$LINUXALL] + } + + $PreLinkEvent [$WINDOWS] + { + $CommandLine "call $SRCDIR\vpc_scripts\valve_p4_edit.cmd $LIBPUBLIC\$(TargetName).lib $SRCDIR" "\n" \ + "$BASE" + } + + $PreLinkEvent [$X360] + { + // Run a pre-link event to clean the .def file from the last link + $CommandLine "if exist xbox\xbox.def del xbox\xbox.def" "\n" \ + "$BASE" + } + + $PostBuildEvent [$X360] + { + // Publish the import lib + $CommandLine "if exist $(TargetDir)$(TargetName).lib copy $(TargetDir)$(TargetName).lib $SRCDIR\lib\public\$(TargetName).lib" "\n" \ + "$BASE" + } + + // tier0/vstdlib traditionally used "lib" prefix though nobody else seems to. + $General [$POSIX] + { + $GameOutputFile "$OUTBINDIR/$_IMPLIB_PREFIX$OUTBINNAME$_DLL_EXT" + } + + $Linker [$POSIX] + { + $OutputFile "$(OBJ_DIR)/$_IMPLIB_PREFIX$OUTBINNAME$_DLL_EXT" + } +} + +$Project +{ + $Folder "Source Files" [$WINDOWS||$X360] + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + } + + $Folder "Link Libraries" + { + -$ImpLib tier0 + -$Lib tier1 + -$Implib vstdlib + + $Lib "$SRCDIR\thirdparty\telemetry\lib\telemetry32.link" [$WIN32] + $Lib "$SRCDIR\thirdparty\telemetry\lib\telemetry64.link" [$WIN64] + + $LibExternal "$SRCDIR/thirdparty/telemetry/lib/libtelemetryx86.link" [$LINUX32] + $LibExternal "$SRCDIR/thirdparty/telemetry/lib/libtelemetryx64.link" [$LINUX64] + } +} + +$Project "tier0" +{ + $Folder "Source Files" + { + $File "assert_dialog.cpp" + $File "assert_dialog.rc" [$WINDOWS] + $File "commandline.cpp" + $File "cpu.cpp" + $File "cpumonitoring.cpp" + $File "cpu_posix.cpp" [$POSIX] + $File "cpu_usage.cpp" + $File "dbg.cpp" + $File "dynfunction.cpp" + $File "etwprof.cpp" [$WINDOWS] + $File "fasttimer.cpp" + $File "InterlockedCompareExchange128.masm" [$WIN64] + { + $Configuration + { + $CustomBuildStep + { + // General + $CommandLine "$QUOTE$(VCInstallDir)bin\x86_amd64\ml64.exe$QUOTE /nologo /c /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" + $Description "Compiling $(InputName).masm" + $Outputs "$(IntDir)\$(InputName).obj" + } + } + } + $File "mem.cpp" + $File "mem_helpers.cpp" + $File "memdbg.cpp" + $File "memstd.cpp" + $File "memvalidate.cpp" + $File "minidump.cpp" + $File "pch_tier0.cpp" + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Create Precompiled Header (/Yc)" + } + } + } + $File "platform.cpp" [$WINDOWS||$X360] + $File "platform_posix.cpp" [$POSIX] + $File "pmc360.cpp" [$X360] + $File "pme.cpp" [$WINDOWS] + $File "pme_posix.cpp" [$POSIX] + $File "PMELib.cpp" [$WINDOWS||$POSIX] + { + $Configuration + { + $Compiler + { + $Create/UsePrecompiledHeader "Not Using Precompiled Headers" + } + } + } + $File "progressbar.cpp" + $File "security.cpp" + $File "systeminformation.cpp" + $File "stacktools.cpp" + $File "thread.cpp" [$WINDOWS||$POSIX] + $File "threadtools.cpp" + $File "tier0_strtools.cpp" + $File "tslist.cpp" + $File "vcrmode.cpp" [$WINDOWS] + $File "vcrmode_posix.cpp" [$POSIX] + $File "vprof.cpp" + $File "win32consoleio.cpp" [$WINDOWS] + $File "../tier1/pathmatch.cpp" [$LINUXALL] + } + + $folder "Header Files" + { + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\tier0\cpumonitoring.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\tier0\dbgflag.h" + $File "$SRCDIR\public\tier0\EventMasks.h" + $File "$SRCDIR\public\tier0\EventModes.h" + $File "$SRCDIR\public\tier0\etwprof.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\tier0\ia32detect.h" + $File "$SRCDIR\public\tier0\icommandline.h" + $File "$SRCDIR\public\tier0\IOCTLCodes.h" + $File "$SRCDIR\public\tier0\K8PerformanceCounters.h" + $File "$SRCDIR\public\tier0\l2cache.h" + $File "$SRCDIR\public\tier0\pmc360.h" [$X360] + $File "$SRCDIR\public\tier0\mem.h" + $File "$SRCDIR\public\tier0\memalloc.h" + $File "$SRCDIR\public\tier0\memdbgoff.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "$SRCDIR\public\tier0\minidump.h" + $File "$SRCDIR\public\tier0\P4PerformanceCounters.h" + $File "$SRCDIR\public\tier0\P5P6PerformanceCounters.h" + $File "pch_tier0.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\PMELib.h" + $File "$SRCDIR\public\tier0\progressbar.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "resource.h" + $File "$SRCDIR\public\tier0\systeminformation.h" + $File "$SRCDIR\public\tier0\threadtools.h" + $File "$SRCDIR\public\tier0\tslist.h" + $File "$SRCDIR\public\tier0\validator.h" + $File "$SRCDIR\public\tier0\valobject.h" + $File "$SRCDIR\public\tier0\valve_off.h" + $File "$SRCDIR\public\tier0\valve_on.h" + $File "$SRCDIR\public\tier0\vcr_shared.h" + $File "$SRCDIR\public\tier0\vcrmode.h" + $File "$SRCDIR\public\tier0\vprof.h" + $File "$SRCDIR\public\tier0\wchartypes.h" + $File "$SRCDIR\public\tier0\xbox_codeline_defines.h" + $File "mem_helpers.h" + } + + $Folder "DESKey" [$WINDOWS] + { + $File "DESKey\ALGO.H" + $File "DESKey\ALGO32.LIB" + $File "DESKey\DK2WIN32.H" + $File "DESKey\DK2WIN32.LIB" + } + + $Folder "Xbox" [$X360] + { + $folder "Source Files" + { + $File "xbox\xbox_console.cpp" + $File "xbox\xbox_system.cpp" + $File "xbox\xbox_win32stubs.cpp" + } + $folder "Header Files" + { + $File "$SRCDIR\common\xbox\xbox_console.h" + $File "$SRCDIR\common\xbox\xbox_core.h" + $File "$SRCDIR\common\xbox\xbox_win32stubs.h" + } + } + + $Folder "Manifest Files" [$WINDOWS] + { + $File "ValveETWProvider.man" + { + $Configuration + { + $CustomBuildStep + { + $CommandLine "..\DevTools\bin\mc.exe -um $(InputFilename) -z $(InputName)Events" + $Description "Compiling ETW manifest file" + $Outputs "$(InputName)Events.h;$(InputName)Events.rc" + } + } + } + } +} diff --git a/tier0/tier0_exclude.vpc b/tier0/tier0_exclude.vpc index ed52197..15dce79 100644 --- a/tier0/tier0_exclude.vpc +++ b/tier0/tier0_exclude.vpc @@ -8,10 +8,11 @@ $Project { $Folder "Link Libraries" { - -$Lib "$LIBPUBLIC\tier0" + // Should match the sites that include this + -$Lib "$LIBPUBLIC\tier0" [$POSIX && !$IS_LIB_PROJECT] } $Folder "Source Files" { -$File "$SRCDIR\public\tier0\memoverride.cpp" - } + } } diff --git a/tier0/tier0_staticlink.vpc b/tier0/tier0_staticlink.vpc new file mode 100644 index 0000000..bf7cf8f --- /dev/null +++ b/tier0/tier0_staticlink.vpc @@ -0,0 +1,3 @@ +$Conditional STATIC_LINK "1" + +$Include "tier0.vpc" diff --git a/tier0/tier0_strtools.cpp b/tier0/tier0_strtools.cpp new file mode 100644 index 0000000..72bf6e5 --- /dev/null +++ b/tier0/tier0_strtools.cpp @@ -0,0 +1,53 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +#include "pch_tier0.h" +#include "tier0_strtools.h" + + +#define TOLOWERC( x ) (( ( x >= 'A' ) && ( x <= 'Z' ) )?( x + 32 ) : x ) +extern "C" +int V_tier0_stricmp(const char *s1, const char *s2 ) +{ + uint8 const *pS1 = ( uint8 const * ) s1; + uint8 const *pS2 = ( uint8 const * ) s2; + for(;;) + { + int c1 = *( pS1++ ); + int c2 = *( pS2++ ); + if ( c1 == c2 ) + { + if ( !c1 ) return 0; + } + else + { + if ( ! c2 ) + { + return c1 - c2; + } + c1 = TOLOWERC( c1 ); + c2 = TOLOWERC( c2 ); + if ( c1 != c2 ) + { + return c1 - c2; + } + } + c1 = *( pS1++ ); + c2 = *( pS2++ ); + if ( c1 == c2 ) + { + if ( !c1 ) return 0; + } + else + { + if ( ! c2 ) + { + return c1 - c2; + } + c1 = TOLOWERC( c1 ); + c2 = TOLOWERC( c2 ); + if ( c1 != c2 ) + { + return c1 - c2; + } + } + } +} diff --git a/tier0/tier0_strtools.h b/tier0/tier0_strtools.h new file mode 100644 index 0000000..9ec63b2 --- /dev/null +++ b/tier0/tier0_strtools.h @@ -0,0 +1,3 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +extern "C" int V_tier0_stricmp(const char *s1, const char *s2 ); diff --git a/tier0/tslist.cpp b/tier0/tslist.cpp new file mode 100644 index 0000000..9cd8191 --- /dev/null +++ b/tier0/tslist.cpp @@ -0,0 +1,538 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "pch_tier0.h" +#include "tier0/tslist.h" +#include +#include +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +namespace TSListTests +{ +int NUM_TEST = 10000; +int NUM_THREADS; +int MAX_THREADS = 8; +int NUM_PROCESSORS = 1; + +CInterlockedInt g_nTested; +CInterlockedInt g_nThreads; +CInterlockedInt g_nPushThreads; +CInterlockedInt g_nPopThreads; +CInterlockedInt g_nPushes; +CInterlockedInt g_nPops; +CTSQueue g_TestQueue; +CTSList g_TestList; +volatile bool g_bStart; +std::list g_ThreadHandles; + +int *g_pTestBuckets; + +CTSListBase g_Test; +TSLNodeBase_t **g_nodes; +int idx = 0; + +const char *g_pListType; + +class CTestOps +{ +public: + virtual void Push( int item ) = 0; + virtual bool Pop( int *pResult ) = 0; + virtual bool Validate() { return true; } + virtual bool IsEmpty() = 0; +}; + +class CQueueOps : public CTestOps +{ + void Push( int item ) + { + g_TestQueue.PushItem( item ); + g_nPushes++; + } + bool Pop( int *pResult ) + { + if ( g_TestQueue.PopItem( pResult ) ) + { + g_nPops++; + return true; + } + return false; + } + bool Validate() + { + return g_TestQueue.ValidateQueue(); + } + bool IsEmpty() + { + return ( g_TestQueue.Count() == 0 ); + } +} g_QueueOps; + +class CListOps : public CTestOps +{ + void Push( int item ) + { + g_TestList.PushItem( item ); + g_nPushes++; + } + bool Pop( int *pResult ) + { + if ( g_TestList.PopItem( pResult ) ) + { + g_nPops++; + return true; + } + return false; + } + bool Validate() + { + return true; + } + bool IsEmpty() + { + return ( g_TestList.Count() == 0 ); + } +} g_ListOps; + +CTestOps *g_pTestOps; + +void ClearBuckets() +{ + memset( g_pTestBuckets, 0, sizeof(int) * NUM_TEST ); +} + +void IncBucket( int i ) +{ + if ( i < NUM_TEST ) // tests can slop over a bit + { + ThreadInterlockedIncrement( &g_pTestBuckets[i] ); + } +} + +void DecBucket( int i ) +{ + if ( i < NUM_TEST ) // tests can slop over a bit + { + ThreadInterlockedDecrement( &g_pTestBuckets[i] ); + } +} + +void ValidateBuckets() +{ + for ( int i = 0; i < NUM_TEST; i++ ) + { + if ( g_pTestBuckets[i] != 0 ) + { + Msg( "Test bucket %d has an invalid value %d\n", i, g_pTestBuckets[i] ); + DebuggerBreakIfDebugging(); + return; + } + } +} + +unsigned PopThreadFunc( void *) +{ + ThreadSetDebugName( "PopThread" ); + g_nPopThreads++; + g_nThreads++; + while ( !g_bStart ) + { + ThreadSleep( 0 ); + } + int ignored; + for (;;) + { + if ( !g_pTestOps->Pop( &ignored ) ) + { + if ( g_nPushThreads == 0 ) + { + // Pop the rest + while ( g_pTestOps->Pop( &ignored ) ) + { + ThreadSleep( 0 ); + } + break; + } + } + } + g_nThreads--; + g_nPopThreads--; + return 0; +} + +unsigned PushThreadFunc( void * ) +{ + ThreadSetDebugName( "PushThread" ); + g_nPushThreads++; + g_nThreads++; + while ( !g_bStart ) + { + ThreadSleep( 0 ); + } + + while ( g_nTested < NUM_TEST ) + { + g_pTestOps->Push( g_nTested ); + g_nTested++; + } + g_nThreads--; + g_nPushThreads--; + return 0; +} + +void TestStart() +{ + g_nTested = 0; + g_nThreads = 0; + g_nPushThreads = 0; + g_nPopThreads = 0; + g_bStart = false; + g_nPops = g_nPushes = 0; + ClearBuckets(); +} + +void TestWait() +{ + while ( g_nThreads < NUM_THREADS ) + { + ThreadSleep( 0 ); + } + g_bStart = true; + while ( g_nThreads > 0 ) + { + ThreadSleep( 50 ); + } +} + +void TestEnd( bool bExpectEmpty = true ) +{ + ValidateBuckets(); + + if ( g_nPops != g_nPushes ) + { + Msg( "FAIL: Not all items popped\n" ); + return; + } + + if ( g_pTestOps->Validate() ) + { + if ( !bExpectEmpty || g_pTestOps->IsEmpty() ) + { + Msg("pass\n"); + } + else + { + Msg("FAIL: !IsEmpty()\n"); + } + } + else + { + Msg("FAIL: !Validate()\n"); + } + while ( g_ThreadHandles.size() ) + { + ThreadJoin( g_ThreadHandles.front(), 0 ); + + ReleaseThreadHandle( g_ThreadHandles.front() ); + g_ThreadHandles.pop_front(); + } +} + + +//-------------------------------------------------- +// +// Shared Tests for CTSQueue and CTSList +// +//-------------------------------------------------- +void PushPopTest() +{ + Msg( "%s test: single thread push/pop, in order... ", g_pListType ); + ClearBuckets(); + g_nTested = 0; + int value; + while ( g_nTested < NUM_TEST ) + { + value = g_nTested++; + g_pTestOps->Push( value ); + IncBucket( value ); + } + + g_pTestOps->Validate(); + + while ( g_pTestOps->Pop( &value ) ) + { + DecBucket( value ); + } + TestEnd(); +} + +void PushPopInterleavedTestGuts() +{ + int value; + for (;;) + { + bool bPush = ( rand() % 2 == 0 ); + if ( bPush && ( value = g_nTested++ ) < NUM_TEST ) + { + g_pTestOps->Push( value ); + IncBucket( value ); + } + else if ( g_pTestOps->Pop( &value ) ) + { + DecBucket( value ); + } + else + { + if ( g_nTested >= NUM_TEST ) + { + break; + } + } + } +} + +void PushPopInterleavedTest() +{ + Msg( "%s test: single thread push/pop, interleaved... ", g_pListType ); + srand( Plat_MSTime() ); + g_nTested = 0; + ClearBuckets(); + PushPopInterleavedTestGuts(); + TestEnd(); +} + +unsigned PushPopInterleavedTestThreadFunc( void * ) +{ + ThreadSetDebugName( "PushPopThread" ); + g_nThreads++; + while ( !g_bStart ) + { + ThreadSleep( 0 ); + } + PushPopInterleavedTestGuts(); + g_nThreads--; + return 0; +} + +void STPushMTPop( bool bDistribute ) +{ + Msg( "%s test: single thread push, multithread pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); + TestStart(); + g_ThreadHandles.push_back( CreateSimpleThread( &PushThreadFunc, NULL ) ); + for ( int i = 0; i < NUM_THREADS - 1; i++ ) + { + ThreadHandle_t hThread = CreateSimpleThread( &PopThreadFunc, NULL ); + g_ThreadHandles.push_back( hThread ); + if ( bDistribute ) + { + int32 mask = 1 << (i % NUM_PROCESSORS); + ThreadSetAffinity( hThread, mask ); + } + } + + TestWait(); + TestEnd(); +} + +void MTPushSTPop( bool bDistribute ) +{ + Msg( "%s test: multithread push, single thread pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); + TestStart(); + g_ThreadHandles.push_back( CreateSimpleThread( &PopThreadFunc, NULL ) ); + for ( int i = 0; i < NUM_THREADS - 1; i++ ) + { + ThreadHandle_t hThread = CreateSimpleThread( &PushThreadFunc, NULL ); + g_ThreadHandles.push_back( hThread ); + if ( bDistribute ) + { + int32 mask = 1 << (i % NUM_PROCESSORS); + ThreadSetAffinity( hThread, mask ); + } + } + + TestWait(); + TestEnd(); +} + +void MTPushMTPop( bool bDistribute ) +{ + Msg( "%s test: multithread push, multithread pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); + TestStart(); + int ct = 0; + for ( int i = 0; i < NUM_THREADS / 2 ; i++ ) + { + ThreadHandle_t hThread = CreateSimpleThread( &PopThreadFunc, NULL ); + g_ThreadHandles.push_back( hThread ); + if ( bDistribute ) + { + int32 mask = 1 << (ct++ % NUM_PROCESSORS); + ThreadSetAffinity( hThread, mask ); + } + } + for ( int i = 0; i < NUM_THREADS / 2 ; i++ ) + { + ThreadHandle_t hThread = CreateSimpleThread( &PushThreadFunc, NULL ); + g_ThreadHandles.push_back( hThread ); + if ( bDistribute ) + { + int32 mask = 1 << (ct++ % NUM_PROCESSORS); + ThreadSetAffinity( hThread, mask ); + } + } + + TestWait(); + TestEnd(); +} + +void MTPushPopPopInterleaved( bool bDistribute ) +{ + Msg( "%s test: multithread interleaved push/pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); + srand( Plat_MSTime() ); + TestStart(); + for ( int i = 0; i < NUM_THREADS; i++ ) + { + ThreadHandle_t hThread = CreateSimpleThread( &PushPopInterleavedTestThreadFunc, NULL ); + g_ThreadHandles.push_back( hThread ); + if ( bDistribute ) + { + int32 mask = 1 << (i % NUM_PROCESSORS); + ThreadSetAffinity( hThread, mask ); + } + } + TestWait(); + TestEnd(); +} + +void MTPushSeqPop( bool bDistribute ) +{ + Msg( "%s test: multithread push, sequential pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); + TestStart(); + for ( int i = 0; i < NUM_THREADS; i++ ) + { + ThreadHandle_t hThread = CreateSimpleThread( &PushThreadFunc, NULL ); + g_ThreadHandles.push_back( hThread ); + if ( bDistribute ) + { + int32 mask = 1 << (i % NUM_PROCESSORS); + ThreadSetAffinity( hThread, mask ); + } + } + + TestWait(); + int ignored; + g_pTestOps->Validate(); + while ( g_pTestOps->Pop( &ignored ) ) + { + } + TestEnd(); +} + +void SeqPushMTPop( bool bDistribute ) +{ + Msg( "%s test: sequential push, multithread pop, %s", g_pListType, bDistribute ? "distributed..." : "no affinity..." ); + TestStart(); + while ( g_nTested++ < NUM_TEST ) + { + g_pTestOps->Push( g_nTested ); + } + for ( int i = 0; i < NUM_THREADS; i++ ) + { + ThreadHandle_t hThread = CreateSimpleThread( &PopThreadFunc, NULL ); + g_ThreadHandles.push_back( hThread ); + if ( bDistribute ) + { + int32 mask = 1 << (i % NUM_PROCESSORS); + ThreadSetAffinity( hThread, mask ); + } + } + + TestWait(); + TestEnd(); +} + +} +void RunSharedTests( int nTests ) +{ + using namespace TSListTests; + + const CPUInformation &pi = *GetCPUInformation(); + NUM_PROCESSORS = pi.m_nLogicalProcessors; + MAX_THREADS = NUM_PROCESSORS * 2; + g_pTestBuckets = new int[NUM_TEST]; + while ( nTests-- ) + { + for ( NUM_THREADS = 2; NUM_THREADS <= MAX_THREADS; NUM_THREADS *= 2) + { + Msg( "\nTesting %d threads:\n", NUM_THREADS ); + PushPopTest(); + PushPopInterleavedTest(); + SeqPushMTPop( false ); + STPushMTPop( false ); + MTPushSeqPop( false ); + MTPushSTPop( false ); + MTPushMTPop( false ); + MTPushPopPopInterleaved( false ); + if ( NUM_PROCESSORS > 1 ) + { + SeqPushMTPop( true ); + STPushMTPop( true ); + MTPushSeqPop( true ); + MTPushSTPop( true ); + MTPushMTPop( true ); + MTPushPopPopInterleaved( true ); + } + } + } + delete[] g_pTestBuckets; +} + +bool RunTSListTests( int nListSize, int nTests ) +{ + using namespace TSListTests; + NUM_TEST = nListSize; + + TSLHead_t foo; + (void)foo; // Avoid warning about unused variable. +#ifdef USE_NATIVE_SLIST + int maxSize = ( 1 << (sizeof( foo.Depth ) * 8) ) - 1; +#else + int maxSize = ( 1 << (sizeof( foo.value.Depth ) * 8) ) - 1; +#endif + if ( NUM_TEST > maxSize ) + { + Msg( "TSList cannot hold more that %d nodes\n", maxSize ); + return false; + } + + + g_pTestOps = &g_ListOps; + g_pListType = "CTSList"; + + RunSharedTests( nTests ); + + Msg("Tests done, purging test memory..." ); + g_TestList.Purge(); + Msg( "done\n"); + return true; +} + +bool RunTSQueueTests( int nListSize, int nTests ) +{ + using namespace TSListTests; + NUM_TEST = nListSize; + + g_pTestOps = &g_QueueOps; + g_pListType = "CTSQueue"; + + RunSharedTests( nTests ); + + Msg("Tests done, purging test memory..." ); + g_TestQueue.Purge(); + Msg( "done\n"); + return true; +} diff --git a/tier0/validator.cpp b/tier0/validator.cpp new file mode 100644 index 0000000..3f08fe0 --- /dev/null +++ b/tier0/validator.cpp @@ -0,0 +1,268 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + + +#include "pch_tier0.h" + +#include "tier0/memblockhdr.h" + + +#ifdef DBGFLAG_VALIDATE + +// we use malloc & free internally in our validation code; turn off the deprecation #defines +#undef malloc +#undef free + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CValidator::CValidator( ) +{ + m_pValObjectFirst = NULL; + m_pValObjectLast = NULL; + m_pValObjectCur = NULL; + m_cpvOwned = 0; + m_bMemLeaks = false; + + // Mark all memory blocks as unclaimed, prior to starting the validation process + CMemBlockHdr *pMemBlockHdr = CMemBlockHdr::PMemBlockHdrFirst( ); + pMemBlockHdr = pMemBlockHdr->PMemBlockHdrNext( ); // Head is just a placeholder + while ( NULL != pMemBlockHdr ) + { + pMemBlockHdr->SetBClaimed( false ); + + pMemBlockHdr = pMemBlockHdr->PMemBlockHdrNext( ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CValidator::~CValidator( ) +{ + CValObject *pValObject = m_pValObjectFirst; + CValObject *pValObjectNext; + while ( NULL != pValObject ) + { + pValObjectNext = pValObject->PValObjectNext( ); + Destruct (pValObject); + free( pValObject ); + pValObject = pValObjectNext; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Call this each time you start a new Validate() function. It creates +// a new CValObject to track the caller. +// Input: pchType - The caller's type (typically a class name) +// pvObj - The caller (typically an object pointer) +// pchName - The caller's individual name (typically a member var of another class) +//----------------------------------------------------------------------------- +void CValidator::Push( tchar *pchType, void *pvObj, tchar *pchName ) +{ + // Create a new ValObject and add it to the linked list + + CValObject *pValObjectNew = (CValObject *) malloc( sizeof (CValObject ) ); + Construct (pValObjectNew); + pValObjectNew->Init( pchType, pvObj, pchName, m_pValObjectCur, m_pValObjectLast ); + m_pValObjectLast = pValObjectNew; + if ( NULL == m_pValObjectFirst ) + m_pValObjectFirst = pValObjectNew; + + // Make this the current object + m_pValObjectCur = pValObjectNew; +} + + +//----------------------------------------------------------------------------- +// Purpose: Call this each time you end a Validate() function. It decrements +// our current structure depth. +//----------------------------------------------------------------------------- +void CValidator::Pop( ) +{ + Assert( NULL != m_pValObjectCur ); + m_pValObjectCur = m_pValObjectCur->PValObjectParent( ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Call this to register each memory block you own. +// Input: pvMem - Memory block you own +//----------------------------------------------------------------------------- +void CValidator::ClaimMemory( void *pvMem ) +{ + if ( NULL == pvMem ) + return; + + // Mark the block as owned + CMemBlockHdr *pMemBlockHdr = CMemBlockHdr::PMemBlockHdrFromPvUser( pvMem ); + pMemBlockHdr->CheckValid( ); + Assert( !pMemBlockHdr->BClaimed( ) ); + pMemBlockHdr->SetBClaimed( true ); + + // Let the current object know about it + Assert( NULL != m_pValObjectCur ); + m_pValObjectCur->ClaimMemoryBlock( pvMem ); + + // Update our counter + m_cpvOwned++; +} + + +//----------------------------------------------------------------------------- +// Purpose: We're done enumerating our objects. Perform any final calculations. +//----------------------------------------------------------------------------- +void CValidator::Finalize( void ) +{ + // Count our memory leaks + CMemBlockHdr *pMemBlockHdr = CMemBlockHdr::PMemBlockHdrFirst( ); + pMemBlockHdr = pMemBlockHdr->PMemBlockHdrNext( ); + m_cpubLeaked = 0; + m_cubLeaked = 0; + while ( NULL != pMemBlockHdr ) + { + if ( !pMemBlockHdr->BClaimed( ) ) + { + m_cpubLeaked++; + m_cubLeaked += pMemBlockHdr->CubUser( ); + m_bMemLeaks = true; + } + + pMemBlockHdr = pMemBlockHdr->PMemBlockHdrNext( ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Render all reported objects to the console +// Input: cubThreshold - Only render object whose children have at least +// cubThreshold bytes allocated +//----------------------------------------------------------------------------- +void CValidator::RenderObjects( int cubThreshold ) +{ + // Walk our object list and render them all to the console + CValObject *pValObject = m_pValObjectFirst; + while ( NULL != pValObject ) + { + if ( pValObject->CubMemTree( ) >= cubThreshold ) + { + for ( int ich = 0; ich < pValObject->NLevel( ); ich++ ) + ConMsg( 2, _T(" ") ); + + ConMsg( 2, _T("%s at 0x%x--> %d blocks = %d bytes\n"), + pValObject->PchType( ), pValObject->PvObj( ), pValObject->CpubMemTree( ), + pValObject->CubMemTree( ) ); + } + + pValObject = pValObject->PValObjectNext( ); + } + + + // Dump a summary to the console + ConMsg( 2, _T("Allocated:\t%d blocks\t%d bytes\n"), CpubAllocated( ), CubAllocated( ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Render any discovered memory leaks to the console +//----------------------------------------------------------------------------- +void CValidator::RenderLeaks( void ) +{ + if ( m_bMemLeaks ) + ConMsg( 1, _T("\n") ); + + // Render any leaked blocks to the console + CMemBlockHdr *pMemBlockHdr = CMemBlockHdr::PMemBlockHdrFirst( ); + pMemBlockHdr = pMemBlockHdr->PMemBlockHdrNext( ); + while ( NULL != pMemBlockHdr ) + { + if ( !pMemBlockHdr->BClaimed( ) ) + { + ConMsg( 1, _T("Leaked mem block: Addr = 0x%x\tSize = %d\n"), + pMemBlockHdr->PvUser( ), pMemBlockHdr->CubUser( ) ); + ConMsg( 1, _T("\tAlloc = %s, line %d\n"), + pMemBlockHdr->PchFile( ), pMemBlockHdr->NLine( ) ); + } + + pMemBlockHdr = pMemBlockHdr->PMemBlockHdrNext( ); + } + + // Dump a summary to the console + if ( 0 != m_cpubLeaked ) + ConMsg( 1, _T("!!!Leaked:\t%d blocks\t%d bytes\n"), m_cpubLeaked, m_cubLeaked ); +} + +//----------------------------------------------------------------------------- +// Purpose: Find the validator object associated with the given real object. +//----------------------------------------------------------------------------- +CValObject *CValidator::FindObject( void * pvObj ) +{ + CValObject *pValObject = m_pValObjectFirst; + CValObject *pValObjectNext; + while ( NULL != pValObject ) + { + pValObjectNext = pValObject->PValObjectNext( ); + if( pvObj == pValObject->PvObj() ) + return pValObject; + + pValObject = pValObjectNext; + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Diff one CValidator against another. Each Validator object is +// tagged with whether it is new since the last snapshot or not. +//----------------------------------------------------------------------------- +void CValidator::DiffAgainst( CValidator *pOtherValidator ) // Removes any entries from this validator that are also present in the other. +{ + // Render any leaked blocks to the console + CValObject *pValObject = m_pValObjectFirst; + CValObject *pValObjectNext; + while ( NULL != pValObject ) + { + pValObjectNext = pValObject->PValObjectNext( ); + pValObject->SetBNewSinceSnapshot( pOtherValidator->FindObject( pValObject->PvObj() ) == NULL ); + + if( pValObject->BNewSinceSnapshot() && pValObject->CubMemTree( ) ) + { + for ( int ich = 0; ich < pValObject->NLevel( ); ich++ ) + ConMsg( 2, _T(" ") ); + + ConMsg( 2, _T("%s at 0x%x--> %d blocks = %d bytes\n"), + pValObject->PchType( ), pValObject->PvObj( ), pValObject->CpubMemTree( ), + pValObject->CubMemTree( ) ); + } + + pValObject = pValObjectNext; + } + +} + +void CValidator::Validate( CValidator &validator, tchar *pchName ) +{ + validator.Push( _T("CValidator"), this, pchName ); + + validator.ClaimMemory( this ); + + // Render any leaked blocks to the console + CValObject *pValObject = m_pValObjectFirst; + CValObject *pValObjectNext; + while ( NULL != pValObject ) + { + pValObjectNext = pValObject->PValObjectNext( ); + validator.ClaimMemory( pValObject ); + pValObject = pValObjectNext; + } + + validator.Pop(); +} + +#endif // DBGFLAG_VALIDATE \ No newline at end of file diff --git a/tier0/valobject.cpp b/tier0/valobject.cpp new file mode 100644 index 0000000..656cdde --- /dev/null +++ b/tier0/valobject.cpp @@ -0,0 +1,120 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "pch_tier0.h" +#include "vstdlib/pch_vstdlib.h" + +#ifdef DBGFLAG_VALIDATE + +//----------------------------------------------------------------------------- +// Purpose: Initializer +// Input: pchType - Type of the object we represent. +// WARNING: pchType must be a static (since we keep a copy of it around for a while) +// pvObj - Pointer to the object we represent +// pchName - Name of the individual object we represent +// WARNING: pchName must be a static (since we keep a copy of it around for a while) +// pValObjectparent- Our parent object (ie, the object that our object is a member of) +// pValObjectPrev - Object that precedes us in the linked list (we're +// always added to the end) +//----------------------------------------------------------------------------- +void CValObject::Init( tchar *pchType, void *pvObj, tchar *pchName, + CValObject *pValObjectParent, CValObject *pValObjectPrev ) +{ + m_nUser = 0; + + // Initialize pchType: + if ( NULL != pchType ) + { + Q_strncpy( m_rgchType, pchType, (int) ( sizeof(m_rgchType) / sizeof(*m_rgchType) ) ); + } + else + { + m_rgchType[0] = '\0'; + } + + m_pvObj = pvObj; + + // Initialize pchName: + if ( NULL != pchName ) + { + Q_strncpy( m_rgchName, pchName, sizeof(m_rgchName) / sizeof(*m_rgchName) ); + } + else + { + m_rgchName[0] = NULL; + } + + m_pValObjectParent = pValObjectParent; + + if ( NULL == pValObjectParent ) + m_nLevel = 0; + else + m_nLevel = pValObjectParent->NLevel( ) + 1; + + m_cpubMemSelf = 0; + m_cubMemSelf = 0; + m_cpubMemTree = 0; + m_cubMemTree = 0; + + // Insert us at the back of the linked list + if ( NULL != pValObjectPrev ) + { + Assert( NULL == pValObjectPrev->m_pValObjectNext ); + pValObjectPrev->m_pValObjectNext = this; + } + m_pValObjectNext = NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CValObject::~CValObject( ) +{ +} + + +//----------------------------------------------------------------------------- +// Purpose: The object we represent has claimed direct ownership of a block of +// memory. Record that we own it. +// Input: pvMem - Address of the memory block +//----------------------------------------------------------------------------- +void CValObject::ClaimMemoryBlock( void *pvMem ) +{ + // Get the memory block header + CMemBlockHdr *pMemBlockHdr = CMemBlockHdr::PMemBlockHdrFromPvUser( pvMem ); + pMemBlockHdr->CheckValid( ); + + // Update our counters + m_cpubMemSelf++; + m_cubMemSelf+= pMemBlockHdr->CubUser( ); + m_cpubMemTree++; + m_cubMemTree+= pMemBlockHdr->CubUser( ); + + // If we have a parent object, let it know about the memory (it'll recursively call up the tree) + if ( NULL != m_pValObjectParent ) + m_pValObjectParent->ClaimChildMemoryBlock( pMemBlockHdr->CubUser( ) ); +} + + +//----------------------------------------------------------------------------- +// Purpose: A child of ours has claimed ownership of a memory block. Make +// a note of it, and pass the message back up the tree. +// Input: cubUser - Size of the memory block +//----------------------------------------------------------------------------- +void CValObject::ClaimChildMemoryBlock( int cubUser ) +{ + m_cpubMemTree++; + m_cubMemTree += cubUser; + + if ( NULL != m_pValObjectParent ) + m_pValObjectParent->ClaimChildMemoryBlock( cubUser ); +} + + + +#endif // DBGFLAG_VALIDATE \ No newline at end of file diff --git a/tier0/valveetwprovider.man b/tier0/valveetwprovider.man new file mode 100644 index 0000000..9b8f8a3 --- /dev/null +++ b/tier0/valveetwprovider.man @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tier0/vcrmode.cpp b/tier0/vcrmode.cpp new file mode 100644 index 0000000..ed8a0f4 --- /dev/null +++ b/tier0/vcrmode.cpp @@ -0,0 +1,1778 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include "pch_tier0.h" + +#ifdef _WIN32 +#define WIN_32_LEAN_AND_MEAN +#include +#include +#endif + +#include +#include +#include +#include +#include +#include "tier0/vcrmode.h" +#include "tier0/dbg.h" + +// FIXME: We totally have a bad tier dependency here +#include "inputsystem/inputenums.h" + +#ifndef NO_VCR + +#define PvRealloc realloc +#define PvAlloc malloc + + + +#define VCR_RuntimeAssert(x) VCR_RuntimeAssertFn(x, #x) + +double g_flLastVCRFloatTimeValue; + +bool g_bExpectingWindowProcCalls = false; + +IVCRHelpers *g_pHelpers = 0; + +FILE *g_pVCRFile = NULL; +VCRMode_t g_VCRMode = VCR_Disabled; +VCRMode_t g_OldVCRMode = VCR_Invalid; // Stored temporarily between SetEnabled(0)/SetEnabled(1) blocks. +int g_iCurEvent = 0; + +int g_CurFilePos = 0; // So it knows when we're done playing back. +int g_FileLen = 0; + +VCREvent g_LastReadEvent = (VCREvent)-1; // Last VCR_ReadEvent() call. +int g_LastEventThread; // The thread index of the thread that g_LastReadEvent is intended for. + +int g_bVCREnabled = 0; + + + +// ------------------------------------------------------------------------------------------ // +// These wrappers exist because for some reason thread-blocking functions nuke the +// last function on the call stack, so it's very hard to debug without these wrappers. +// ------------------------------------------------------------------------------------------ // +inline unsigned long Wrap_WaitForSingleObject( HANDLE hObj, DWORD duration ) +{ + return WaitForSingleObject( hObj, duration ); +} + +inline unsigned long Wrap_WaitForMultipleObjects( uint32 nHandles, const void **pHandles, int bWaitAll, uint32 timeout ) +{ + return WaitForMultipleObjects( nHandles, (const HANDLE *)pHandles, bWaitAll, timeout ); +} + +inline void Wrap_EnterCriticalSection( CRITICAL_SECTION *pSection ) +{ + EnterCriticalSection( pSection ); +} + + + +// ------------------------------------------------------------------------------------------ // +// Threadsafe debugging file output. +// ------------------------------------------------------------------------------------------ // +FILE *g_pDebugFile = 0; +CRITICAL_SECTION g_DebugFileCS; + +class CCSInit +{ +public: + CCSInit() + { + InitializeCriticalSection( &g_DebugFileCS ); + } + ~CCSInit() + { + DeleteCriticalSection( &g_DebugFileCS ); + } +} g_DebugFileCS222; + +void VCR_Debug( const char *pMsg, ... ) +{ + va_list marker; + va_start( marker, pMsg ); + + EnterCriticalSection( &g_DebugFileCS ); + + if ( !g_pDebugFile ) + g_pDebugFile = fopen( "c:\\vcrdebug.txt", "wt" ); + + if ( g_pDebugFile ) + { + vfprintf( g_pDebugFile, pMsg, marker ); + fflush( g_pDebugFile ); + } + + LeaveCriticalSection( &g_DebugFileCS ); + + va_end( marker ); +} + + + +// ------------------------------------------------------------------------------------------ // +// VCR threading support. +// It uses 2 methods to implement threading, depending on whether you're recording or not. +// +// If you're recording, it uses critical sections to control access to the events written +// into the file. +// +// During playback, every thread waits on a windows event handle. When a VCR event is done +// being read out, it peeks ahead and sees which thread should get the next VCR event +// and it wakes up that thread. +// ------------------------------------------------------------------------------------------ // + +#define MAX_VCR_THREADS 512 +class CVCRThreadInfo +{ +public: + DWORD m_ThreadID; // The Windows thread ID. + HANDLE m_hWaitEvent; // Used to get the signal that there is an event for this thread. + bool m_bEnabled; // By default, this is true, but it can be set to false to temporarily disable a thread's VCR usage. +}; +CVCRThreadInfo *g_pVCRThreads = NULL; // This gets allocated to MAX_VCR_THREADS size if we're doing any VCR recording or playback. +int g_nVCRThreads = 0; + +// Used to avoid writing the thread ID into events that are for the main thread. +DWORD g_VCRMainThreadID = 0; + +// Set to true if VCR_Start is ever called. +bool g_bVCRStartCalled = false; + + +unsigned short GetCurrentVCRThreadIndex() +{ + DWORD hCurThread = GetCurrentThreadId(); + for ( int i=0; i < g_nVCRThreads; i++ ) + { + if ( g_pVCRThreads[i].m_ThreadID == hCurThread ) + return (unsigned short)i; + } + Error( "GetCurrentVCRThreadInfo: no matching thread." ); + return 0; +} + + +CVCRThreadInfo* GetCurrentVCRThreadInfo() +{ + return &g_pVCRThreads[ GetCurrentVCRThreadIndex() ]; +} + + +static void VCR_SignalNextEvent(); + + +// ------------------------------------------------------------------------------------------ // +// This manages which thread gets the next event. +// ------------------------------------------------------------------------------------------ // + +CRITICAL_SECTION g_VCRCriticalSection; + +class CVCRThreadSafe +{ +public: + CVCRThreadSafe() + { + m_bSignalledNextEvent = false; + + if ( g_VCRMode == VCR_Record ) + { + Wrap_EnterCriticalSection( &g_VCRCriticalSection ); + } + else if ( g_VCRMode == VCR_Playback ) + { + // Wait until our event is signalled, telling us that we are the next guy in line for an event. + WaitForSingleObject( GetCurrentVCRThreadInfo()->m_hWaitEvent, INFINITE ); + } + } + ~CVCRThreadSafe() + { + if ( g_VCRMode == VCR_Record ) + { + LeaveCriticalSection( &g_VCRCriticalSection ); + } + else if ( g_VCRMode == VCR_Playback && !m_bSignalledNextEvent ) + { + // Set the event for the next thread's VCR event. + VCR_SignalNextEvent(); + } + } + void SignalNextEvent() + { + VCR_SignalNextEvent(); + m_bSignalledNextEvent = true; + } + +private: + bool m_bSignalledNextEvent; +}; + +class CVCRThreadSafeInitter +{ +public: + CVCRThreadSafeInitter() + { + InitializeCriticalSection( &g_VCRCriticalSection ); + } + ~CVCRThreadSafeInitter() + { + DeleteCriticalSection( &g_VCRCriticalSection ); + } +} g_VCRThreadSafeInitter; + +#define VCR_THREADSAFE CVCRThreadSafe vcrThreadSafe; + + + + +// ---------------------------------------------------------------------- // +// Internal functions. +// ---------------------------------------------------------------------- // + +static void VCR_Error( const char *pFormat, ... ) +{ + #ifdef _DEBUG + // Figure out which thread we're in, for the debugger. + DWORD curThreadId = GetCurrentThreadId(); + int iCurThread = -1; + for ( int i=0; i < g_nVCRThreads; i++ ) + { + if ( g_pVCRThreads[i].m_ThreadID == curThreadId ) + iCurThread = i; + } + + DebuggerBreak(); + #endif + + char str[256]; + va_list marker; + va_start( marker, pFormat ); + _vsnprintf( str, sizeof( str ), pFormat, marker ); + va_end( marker ); + + g_pHelpers->ErrorMessage( str ); + VCREnd(); +} + +static void VCR_RuntimeAssertFn(int bAssert, char const *pStr) +{ + if(!bAssert) + { + VCR_Error( "*** VCR ASSERT FAILED: %s ***\n", pStr ); + } +} + +static void VCR_Read(void *pDest, int size) +{ + if(!g_pVCRFile) + { + memset(pDest, 0, size); + return; + } + + fread(pDest, 1, size, g_pVCRFile); + + g_CurFilePos += size; + + VCR_RuntimeAssert(g_CurFilePos <= g_FileLen); + + if(g_CurFilePos >= g_FileLen) + { + VCREnd(); + } +} + +template +static void VCR_ReadVal(T &val) +{ + VCR_Read(&val, sizeof(val)); +} + +static void VCR_Write(void const *pSrc, int size) +{ + fwrite(pSrc, 1, size, g_pVCRFile); + fflush(g_pVCRFile); +} + +template +static void VCR_WriteVal(T &val) +{ + VCR_Write(&val, sizeof(val)); +} + + + +void VCR_SignalNextEvent() +{ + // When this function is called, we know that we are the only thread that is accessing the VCR file. + unsigned char event; + VCR_Read( &event, 1 ); + + // Verify that we're in the correct thread for this event. + unsigned short threadID; + if ( event & 0x80 ) + { + VCR_ReadVal( threadID ); + event &= ~0x80; + } + else + { + threadID = 0; + } + + // Must be a valid thread ID. + if ( threadID >= g_nVCRThreads ) + { + Error( "VCR_ReadEvent: invalid threadID (%d).", threadID ); + } + + // Now signal the next thread. + g_LastReadEvent = (VCREvent)event; + g_LastEventThread = threadID; + SetEvent( g_pVCRThreads[threadID].m_hWaitEvent ); +} + + +static VCREvent VCR_ReadEvent() +{ + return g_LastReadEvent; +} + + +static void VCR_WriteEvent( VCREvent event ) +{ + unsigned char cEvent = (unsigned char)event; + + unsigned short threadID = GetCurrentVCRThreadIndex(); + if ( threadID == 0 ) + { + VCR_Write( &cEvent, 1 ); + } + else + { + cEvent |= 0x80; + VCR_Write( &cEvent, 1 ); + + VCR_WriteVal( threadID ); + } +} + +static void VCR_IncrementEvent() +{ + ++g_iCurEvent; +} + +static void VCR_Event(VCREvent type) +{ + if ( g_VCRMode == VCR_Disabled ) + return; + + VCR_IncrementEvent(); + if(g_VCRMode == VCR_Record) + { + VCR_WriteEvent(type); + } + else + { + VCREvent currentEvent = VCR_ReadEvent(); + VCR_RuntimeAssert( currentEvent == type ); + } +} + + +// ---------------------------------------------------------------------- // +// VCR trace interface. +// ---------------------------------------------------------------------- // + +class CVCRTrace : public IVCRTrace +{ +public: + virtual VCREvent ReadEvent() + { + return VCR_ReadEvent(); + } + + virtual void Read( void *pDest, int size ) + { + VCR_Read( pDest, size ); + } +}; + +static CVCRTrace g_VCRTrace; + + +// ---------------------------------------------------------------------- // +// VCR interface. +// ---------------------------------------------------------------------- // + +static int VCR_Start( char const *pFilename, bool bRecord, IVCRHelpers *pHelpers ) +{ + unsigned long version; + + g_VCRMainThreadID = GetCurrentThreadId(); + g_bVCRStartCalled = true; + + + // Setup the initial VCR thread list. + g_pVCRThreads = new CVCRThreadInfo[MAX_VCR_THREADS]; + g_pVCRThreads[0].m_ThreadID = GetCurrentThreadId(); + g_pVCRThreads[0].m_hWaitEvent = CreateEvent( NULL, false, false, NULL ); + g_pVCRThreads[0].m_bEnabled = true; + g_nVCRThreads = 1; + + + g_pHelpers = pHelpers; + + VCREnd(); + + g_OldVCRMode = VCR_Invalid; + if ( bRecord ) + { + char *pCommandLine = GetCommandLine(); + if ( !strstr( pCommandLine, "-nosound" ) ) + Error( "VCR record: must use -nosound." ); + + g_pVCRFile = fopen( pFilename, "wb" ); + if( g_pVCRFile ) + { + // Write the version. + version = VCRFILE_VERSION; + VCR_Write(&version, sizeof(version)); + + g_VCRMode = VCR_Record; + return TRUE; + } + else + { + return FALSE; + } + } + else + { + g_pVCRFile = fopen( pFilename, "rb" ); + if( g_pVCRFile ) + { + // Get the file length. + fseek(g_pVCRFile, 0, SEEK_END); + g_FileLen = ftell(g_pVCRFile); + fseek(g_pVCRFile, 0, SEEK_SET); + g_CurFilePos = 0; + + // Verify the file version. + VCR_Read(&version, sizeof(version)); + if(version != VCRFILE_VERSION) + { + assert(!"VCR_Start: invalid file version"); + VCREnd(); + return FALSE; + } + + g_VCRMode = VCR_Playback; + VCR_SignalNextEvent(); // Signal the first thread for its event. + return TRUE; + } + else + { + return FALSE; + } + } +} + + +static void VCR_End() +{ + if ( g_pVCRFile ) + { + fclose(g_pVCRFile); + g_pVCRFile = NULL; + } + + if ( g_VCRMode == VCR_Playback ) + { + // It's going to get screwy now, especially if we have threads, so just exit. + #ifdef _DEBUG + if ( IsDebuggerPresent() ) + DebuggerBreak(); + #endif + + TerminateProcess( GetCurrentProcess(), 1 ); + } + + g_VCRMode = VCR_Disabled; +} + + +static IVCRTrace* VCR_GetVCRTraceInterface() +{ + return &g_VCRTrace; +} + + +static VCRMode_t VCR_GetMode() +{ + return g_VCRMode; +} + + +static void VCR_SetEnabled( int bEnabled ) +{ + if ( g_VCRMode != VCR_Disabled ) + { + g_pVCRThreads[ GetCurrentVCRThreadIndex() ].m_bEnabled = (bEnabled != 0); + } +} + + +inline bool IsVCRModeEnabledForThisThread() +{ + if ( g_VCRMode == VCR_Disabled || !g_bVCRStartCalled ) + return false; + + return g_pVCRThreads[ GetCurrentVCRThreadIndex() ].m_bEnabled; +} + + +static void VCR_SyncToken(char const *pToken) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + unsigned char len; + + VCR_THREADSAFE; + VCR_Event(VCREvent_SyncToken); + + if(g_VCRMode == VCR_Record) + { + int intLen = strlen( pToken ); + assert( intLen <= 255 ); + + len = (unsigned char)intLen; + + VCR_Write(&len, 1); + VCR_Write(pToken, len); + } + else if(g_VCRMode == VCR_Playback) + { + char test[256]; + + VCR_Read(&len, 1); + VCR_Read(test, len); + + VCR_RuntimeAssert( len == (unsigned char)strlen(pToken) ); + VCR_RuntimeAssert( memcmp(pToken, test, len) == 0 ); + } +} + + +static double VCR_Hook_Sys_FloatTime(double time) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return time; + + VCR_THREADSAFE; + VCR_Event(VCREvent_Sys_FloatTime); + + if(g_VCRMode == VCR_Record) + { + VCR_Write(&time, sizeof(time)); + } + else if(g_VCRMode == VCR_Playback) + { + VCR_Read(&time, sizeof(time)); + g_flLastVCRFloatTimeValue = time; + } + + return time; +} + + + +static int VCR_Hook_PeekMessage( + struct tagMSG *msg, + void *hWnd, + unsigned int wMsgFilterMin, + unsigned int wMsgFilterMax, + unsigned int wRemoveMsg + ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return PeekMessage((MSG*)msg, (HWND)hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); + + VCR_THREADSAFE; + + if( g_VCRMode == VCR_Record ) + { + // The trapped windowproc calls should be flushed by the time we get here. + int ret; + ret = PeekMessage( (MSG*)msg, (HWND)hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg ); + + // NOTE: this must stay AFTER the trapped window proc calls or things get + // read back in the wrong order. + VCR_Event( VCREvent_PeekMessage ); + + VCR_WriteVal(ret); + if(ret) + VCR_Write(msg, sizeof(MSG)); + + return ret; + } + else + { + Assert( g_VCRMode == VCR_Playback ); + + // Playback any windows messages that got trapped. + VCR_Event( VCREvent_PeekMessage ); + + int ret; + VCR_ReadVal(ret); + if(ret) + VCR_Read(msg, sizeof(MSG)); + + return ret; + } +} + + +void VCR_Hook_RecordGameMsg( const InputEvent_t& event ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + VCR_THREADSAFE; + + if ( g_VCRMode == VCR_Record ) + { + VCR_Event( VCREvent_GameMsg ); + + char val = 1; + VCR_WriteVal( val ); + VCR_WriteVal( event.m_nType ); + VCR_WriteVal( event.m_nData ); + VCR_WriteVal( event.m_nData2 ); + VCR_WriteVal( event.m_nData3 ); + } +} + + +void VCR_Hook_RecordEndGameMsg() +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + VCR_THREADSAFE; + + if ( g_VCRMode == VCR_Record ) + { + VCR_Event( VCREvent_GameMsg ); + char val = 0; + VCR_WriteVal( val ); // record that there are no more messages. + } +} + + +bool VCR_Hook_PlaybackGameMsg( InputEvent_t* pEvent ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return false; + + VCR_THREADSAFE; + + if ( g_VCRMode == VCR_Playback ) + { + VCR_Event( VCREvent_GameMsg ); + + char bMsg; + VCR_ReadVal( bMsg ); + if ( bMsg ) + { + VCR_ReadVal( pEvent->m_nType ); + VCR_ReadVal( pEvent->m_nData ); + VCR_ReadVal( pEvent->m_nData2 ); + VCR_ReadVal( pEvent->m_nData3 ); + return true; + } + } + + return false; +} + + +static void VCR_Hook_GetCursorPos(struct tagPOINT *pt) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + { + GetCursorPos(pt); + return; + } + + VCR_THREADSAFE; + + VCR_Event(VCREvent_GetCursorPos); + + if(g_VCRMode == VCR_Playback) + { + VCR_ReadVal(*pt); + } + else + { + GetCursorPos(pt); + + if(g_VCRMode == VCR_Record) + { + VCR_WriteVal(*pt); + } + } +} + + +static void VCR_Hook_ScreenToClient(void *hWnd, struct tagPOINT *pt) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + { + ScreenToClient((HWND)hWnd, pt); + return; + } + + VCR_THREADSAFE; + VCR_Event(VCREvent_ScreenToClient); + + if(g_VCRMode == VCR_Playback) + { + VCR_ReadVal(*pt); + } + else + { + ScreenToClient((HWND)hWnd, pt); + + if(g_VCRMode == VCR_Record) + { + VCR_WriteVal(*pt); + } + } +} + + +static int VCR_Hook_recvfrom(int s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + { + return recvfrom((SOCKET)s, buf, len, flags, from, fromlen); + } + + VCR_THREADSAFE; + VCR_Event(VCREvent_recvfrom); + + int ret; + if ( g_VCRMode == VCR_Playback ) + { + // Get the result from our file. + VCR_Read(&ret, sizeof(ret)); + if(ret == SOCKET_ERROR) + { + int err; + VCR_ReadVal(err); + WSASetLastError(err); + } + else + { + VCR_Read( buf, ret ); + + char bFrom; + VCR_ReadVal( bFrom ); + if ( bFrom ) + { + VCR_Read( from, *fromlen ); + } + } + } + else + { + ret = recvfrom((SOCKET)s, buf, len, flags, from, fromlen); + + if ( g_VCRMode == VCR_Record ) + { + // Record the result. + VCR_Write(&ret, sizeof(ret)); + if(ret == SOCKET_ERROR) + { + int err = WSAGetLastError(); + VCR_WriteVal(err); + } + else + { + VCR_Write( buf, ret ); + + char bFrom = !!from; + VCR_WriteVal( bFrom ); + if ( bFrom ) + VCR_Write( from, *fromlen ); + } + } + } + + return ret; +} + + +static int VCR_Hook_recv(int s, char *buf, int len, int flags) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + { + return recv( (SOCKET)s, buf, len, flags ); + } + + VCR_THREADSAFE; + VCR_Event(VCREvent_recv); + + int ret; + if ( g_VCRMode == VCR_Playback ) + { + // Get the result from our file. + VCR_Read(&ret, sizeof(ret)); + if(ret == SOCKET_ERROR) + { + int err; + VCR_ReadVal(err); + WSASetLastError(err); + } + else + { + VCR_Read( buf, ret ); + } + } + else + { + ret = recv( (SOCKET)s, buf, len, flags ); + + if ( g_VCRMode == VCR_Record ) + { + // Record the result. + VCR_Write(&ret, sizeof(ret)); + if(ret == SOCKET_ERROR) + { + int err = WSAGetLastError(); + VCR_WriteVal(err); + } + else + { + VCR_Write( buf, ret ); + } + } + } + + return ret; +} + + +static int VCR_Hook_send(int s, const char *buf, int len, int flags) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + { + return send( (SOCKET)s, buf, len, flags ); + } + + VCR_THREADSAFE; + VCR_Event(VCREvent_send); + + int ret; + if ( g_VCRMode == VCR_Playback ) + { + // Get the result from our file. + VCR_Read(&ret, sizeof(ret)); + if(ret == SOCKET_ERROR) + { + int err; + VCR_ReadVal(err); + WSASetLastError(err); + } + } + else + { + ret = send( (SOCKET)s, buf, len, flags ); + + if ( g_VCRMode == VCR_Record ) + { + // Record the result. + VCR_Write(&ret, sizeof(ret)); + if(ret == SOCKET_ERROR) + { + int err = WSAGetLastError(); + VCR_WriteVal(err); + } + } + } + + return ret; +} + + +static void VCR_Hook_Cmd_Exec(char **f) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + VCR_THREADSAFE; + VCR_Event(VCREvent_Cmd_Exec); + + if(g_VCRMode == VCR_Playback) + { + int len; + + VCR_Read(&len, sizeof(len)); + if(len == -1) + { + *f = NULL; + } + else + { + *f = (char*)PvAlloc(len); + VCR_Read(*f, len); + } + } + else if(g_VCRMode == VCR_Record) + { + int len; + char *str = *f; + + if(str) + { + len = strlen(str)+1; + VCR_Write(&len, sizeof(len)); + VCR_Write(str, len); + } + else + { + len = -1; + VCR_Write(&len, sizeof(len)); + } + } +} + + +static char* VCR_Hook_GetCommandLine() +{ + // This function is special in that it can be called before VCR mode is initialized. + // In this special case, just return the command line. + if ( !g_pVCRThreads ) + return GetCommandLine(); + + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return GetCommandLine(); + + VCR_THREADSAFE; + VCR_Event(VCREvent_CmdLine); + + int len; + char *ret; + + if(g_VCRMode == VCR_Playback) + { + VCR_Read(&len, sizeof(len)); + ret = new char[len]; + VCR_Read(ret, len); + } + else + { + ret = GetCommandLine(); + + if(g_VCRMode == VCR_Record) + { + len = strlen(ret) + 1; + VCR_WriteVal(len); + VCR_Write(ret, len); + } + } + + return ret; +} + + +static long VCR_Hook_RegOpenKeyEx( void *hKey, const char *lpSubKey, unsigned long ulOptions, unsigned long samDesired, void *pHKey ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return RegOpenKeyEx( (HKEY)hKey, lpSubKey, ulOptions, samDesired, (PHKEY)pHKey ); + + VCR_THREADSAFE; + VCR_Event(VCREvent_RegOpenKeyEx); + + long ret; + if(g_VCRMode == VCR_Playback) + { + VCR_ReadVal(ret); // (don't actually write anything to the person's registry when playing back). + } + else + { + ret = RegOpenKeyEx( (HKEY)hKey, lpSubKey, ulOptions, samDesired, (PHKEY)pHKey ); + + if(g_VCRMode == VCR_Record) + VCR_WriteVal(ret); + } + + return ret; +} + + +static long VCR_Hook_RegSetValueEx(void *hKey, tchar const *lpValueName, unsigned long Reserved, unsigned long dwType, unsigned char const *lpData, unsigned long cbData) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return RegSetValueEx((HKEY)hKey, lpValueName, Reserved, dwType, lpData, cbData); + + VCR_THREADSAFE; + VCR_Event(VCREvent_RegSetValueEx); + + long ret; + if(g_VCRMode == VCR_Playback) + { + VCR_ReadVal(ret); // (don't actually write anything to the person's registry when playing back). + } + else + { + ret = RegSetValueEx((HKEY)hKey, lpValueName, Reserved, dwType, lpData, cbData); + + if(g_VCRMode == VCR_Record) + VCR_WriteVal(ret); + } + + return ret; +} + + +static long VCR_Hook_RegQueryValueEx(void *hKey, tchar const *lpValueName, unsigned long *lpReserved, unsigned long *lpType, unsigned char *lpData, unsigned long *lpcbData) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return RegQueryValueEx((HKEY)hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); + + VCR_THREADSAFE; + VCR_Event(VCREvent_RegQueryValueEx); + + // Doesn't support this being null right now (although it would be trivial to add support). + assert(lpData); + + long ret; + unsigned long dummy = 0; + if(g_VCRMode == VCR_Playback) + { + VCR_ReadVal(ret); + VCR_ReadVal(lpType ? *lpType : dummy); + VCR_ReadVal(*lpcbData); + VCR_Read(lpData, *lpcbData); + } + else + { + ret = RegQueryValueEx((HKEY)hKey, lpValueName, lpReserved, lpType, lpData, lpcbData); + + if(g_VCRMode == VCR_Record) + { + VCR_WriteVal(ret); + VCR_WriteVal(lpType ? *lpType : dummy); + VCR_WriteVal(*lpcbData); + VCR_Write(lpData, *lpcbData); + } + } + + return ret; +} + + +static long VCR_Hook_RegCreateKeyEx(void *hKey, char const *lpSubKey, unsigned long Reserved, char *lpClass, unsigned long dwOptions, + unsigned long samDesired, void *lpSecurityAttributes, void *phkResult, unsigned long *lpdwDisposition) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return RegCreateKeyEx((HKEY)hKey, lpSubKey, Reserved, lpClass, dwOptions, samDesired, (LPSECURITY_ATTRIBUTES)lpSecurityAttributes, (HKEY*)phkResult, lpdwDisposition); + + VCR_THREADSAFE; + VCR_Event(VCREvent_RegCreateKeyEx); + + long ret; + if(g_VCRMode == VCR_Playback) + { + VCR_ReadVal(ret); // (don't actually write anything to the person's registry when playing back). + } + else + { + ret = RegCreateKeyEx((HKEY)hKey, lpSubKey, Reserved, lpClass, dwOptions, samDesired, (LPSECURITY_ATTRIBUTES)lpSecurityAttributes, (HKEY*)phkResult, lpdwDisposition); + + if(g_VCRMode == VCR_Record) + VCR_WriteVal(ret); + } + + return ret; +} + + +static void VCR_Hook_RegCloseKey(void *hKey) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + { + RegCloseKey( (HKEY)hKey ); + return; + } + + VCR_THREADSAFE; + VCR_Event(VCREvent_RegCloseKey); + + if(g_VCRMode == VCR_Playback) + { + } + else + { + RegCloseKey((HKEY)hKey); + } +} + + +int VCR_Hook_GetNumberOfConsoleInputEvents( void *hInput, unsigned long *pNumEvents ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return GetNumberOfConsoleInputEvents( (HANDLE)hInput, pNumEvents ); + + VCR_THREADSAFE; + VCR_Event( VCREvent_GetNumberOfConsoleInputEvents ); + + char ret; + if ( g_VCRMode == VCR_Playback ) + { + VCR_ReadVal( ret ); + VCR_ReadVal( *pNumEvents ); + } + else + { + ret = (char)GetNumberOfConsoleInputEvents( (HANDLE)hInput, pNumEvents ); + + if ( g_VCRMode == VCR_Record ) + { + VCR_WriteVal( ret ); + VCR_WriteVal( *pNumEvents ); + } + } + + return ret; +} + + +int VCR_Hook_ReadConsoleInput( void *hInput, void *pRecs, int nMaxRecs, unsigned long *pNumRead ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return ReadConsoleInput( (HANDLE)hInput, (INPUT_RECORD*)pRecs, nMaxRecs, pNumRead ); + + VCR_THREADSAFE; + VCR_Event( VCREvent_ReadConsoleInput ); + + char ret; + if ( g_VCRMode == VCR_Playback ) + { + VCR_ReadVal( ret ); + if ( ret ) + { + VCR_ReadVal( *pNumRead ); + VCR_Read( pRecs, *pNumRead * sizeof( INPUT_RECORD ) ); + } + } + else + { + ret = (char)ReadConsoleInput( (HANDLE)hInput, (INPUT_RECORD*)pRecs, nMaxRecs, pNumRead ); + + if ( g_VCRMode == VCR_Record ) + { + VCR_WriteVal( ret ); + if ( ret ) + { + VCR_WriteVal( *pNumRead ); + VCR_Write( pRecs, *pNumRead * sizeof( INPUT_RECORD ) ); + } + } + } + + return ret; +} + + +void VCR_Hook_LocalTime( struct tm *today ) +{ + // We just provide a wrapper on this function so we can protect access to time() everywhere. + time_t ltime; + time( <ime ); + tm *pTime = localtime( <ime ); + memcpy( today, pTime, sizeof( *today ) ); + + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + VCR_THREADSAFE; + + VCR_Event( VCREvent_LocalTime ); + if ( g_VCRMode == VCR_Playback ) + { + VCR_Read( today, sizeof( *today ) ); + } + else if ( g_VCRMode == VCR_Record ) + { + VCR_Write( today, sizeof( *today ) ); + } +} + + +void VCR_Hook_Time( long *today ) +{ + // We just provide a wrapper on this function so we can protect access to time() everywhere. + // NOTE: For 64-bit systems we should eventually get a function that takes a time_t, but we should have + // until about 2038 to do that before we overflow a long. + time_t curTime; + time( &curTime ); + + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + { + *today = (long)curTime; + return; + } + + VCR_THREADSAFE; + + VCR_Event( VCREvent_Time ); + if ( g_VCRMode == VCR_Playback ) + { + VCR_Read( &curTime, sizeof( curTime ) ); + } + else if ( g_VCRMode == VCR_Record ) + { + VCR_Write( &curTime, sizeof( curTime ) ); + } + + *today = (long)curTime; +} + + +short VCR_Hook_GetKeyState( int nVirtKey ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return ::GetKeyState( nVirtKey ); + + VCR_THREADSAFE; + VCR_Event( VCREvent_GetKeyState ); + + short ret; + if ( g_VCRMode == VCR_Playback ) + { + VCR_ReadVal( ret ); + } + else + { + ret = ::GetKeyState( nVirtKey ); + if ( g_VCRMode == VCR_Record ) + VCR_WriteVal( ret ); + } + + return ret; +} + + +void VCR_GenericRecord( const char *pEventName, const void *pData, int len ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + VCR_THREADSAFE; + VCR_Event( VCREvent_Generic ); + + if ( g_VCRMode != VCR_Record ) + Error( "VCR_GenericRecord( %s ): not recording a VCR file", pEventName ); + + // Write the event name (or 255 if none). + int nameLen = 255; + if ( pEventName ) + { + nameLen = strlen( pEventName ) + 1; + if ( nameLen >= 255 ) + { + VCR_Error( "VCR_GenericRecord( %s ): nameLen too long (%d)", pEventName, nameLen ); + return; + } + } + unsigned char ucNameLen = (unsigned char)nameLen; + VCR_WriteVal( ucNameLen ); + VCR_Write( pEventName, ucNameLen ); + + // Write the data. + VCR_WriteVal( len ); + VCR_Write( pData, len ); +} + + +int VCR_GenericPlaybackInternal( const char *pEventName, void *pOutData, int maxLen, bool bForceSameLen, bool bForceSameContents ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() || g_VCRMode != VCR_Playback ) + Error( "VCR_Playback( %s ): not playing back a VCR file", pEventName ); + + VCR_THREADSAFE; + VCR_Event( VCREvent_Generic ); + + unsigned char nameLen; + VCR_ReadVal( nameLen ); + if ( nameLen != 255 ) + { + char testName[512]; + VCR_Read( testName, nameLen ); + if ( strcmp( pEventName, testName ) != 0 ) + { + VCR_Error( "VCR_GenericPlayback( %s ) - event name does not match '%s'", pEventName, testName ); + return 0; + } + } + + int dataLen; + VCR_ReadVal( dataLen ); + if ( dataLen > maxLen ) + { + VCR_Error( "VCR_GenericPlayback( %s ) - generic data too long (greater than maxLen: %d)", pEventName, maxLen ); + return 0; + } + else if ( bForceSameLen && dataLen != maxLen ) + { + VCR_Error( "VCR_GenericPlayback( %s ) - data size in file (%d) different than desired (%d)", pEventName, dataLen, maxLen ); + return 0; + } + + if ( bForceSameContents ) + { + if ( !bForceSameLen ) + Error( "bForceSameContents and !bForceSameLen not allowed." ); + + static char *pTempData = new char[dataLen]; + static int tempDataLen = dataLen; + if ( tempDataLen < dataLen ) + { + delete [] pTempData; + pTempData = new char[dataLen]; + tempDataLen = dataLen; + } + + VCR_Read( pTempData, dataLen ); + if ( memcmp( pTempData, pOutData, dataLen ) != 0 ) + { + VCR_Error( "VCR_GenericPlayback: data doesn't match on playback." ); + } + } + else + { + VCR_Read( pOutData, dataLen ); + } + + return dataLen; +} + + +int VCR_GenericPlayback( const char *pEventName, void *pOutData, int maxLen, bool bForceSameLen ) +{ + return VCR_GenericPlaybackInternal( pEventName, pOutData, maxLen, bForceSameLen, false ); +} + + +void VCR_GenericValue( const char *pEventName, void *pData, int maxLen ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + if ( !pEventName ) + pEventName = ""; + + if ( g_VCRMode == VCR_Record ) + VCR_GenericRecord( pEventName, pData, maxLen ); + else if ( g_VCRMode == VCR_Playback ) + VCR_GenericPlaybackInternal( pEventName, pData, maxLen, true, false ); +} + + +void VCR_GenericValueVerify( const tchar *pEventName, const void *pData, int maxLen ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + if ( !pEventName ) + pEventName = ""; + + if ( g_VCRMode == VCR_Record ) + VCR_GenericRecord( pEventName, pData, maxLen ); + else if ( g_VCRMode == VCR_Playback ) + VCR_GenericPlaybackInternal( pEventName, (void*)pData, maxLen, true, true ); +} + + +void WriteShortString( const char *pStr ) +{ + int len = strlen( pStr ) + 1; + if ( len >= 0xFFFF ) + { + Error( "VCR_WriteShortString, string too long (%d characters).", len ); + } + + unsigned short twobytes = (unsigned short)len; + VCR_WriteVal( twobytes ); + VCR_Write( pStr, len ); +} + + +void ReadAndVerifyShortString( const char *pStr ) +{ + int len = strlen( pStr ) + 1; + + unsigned short incomingSize; + VCR_ReadVal( incomingSize ); + + if ( incomingSize != len ) + VCR_Error( "ReadAndVerifyShortString (%s), lengths different.", pStr ); + + static char *pTempData = 0; + static int tempDataLen = 0; + if ( tempDataLen < len ) + { + delete [] pTempData; + pTempData = new char[len]; + tempDataLen = len; + } + + VCR_Read( pTempData, len ); + if ( memcmp( pTempData, pStr, len ) != 0 ) + { + VCR_Error( "ReadAndVerifyShortString: strings different ('%s' vs '%s').", pStr, pTempData ); + } +} + + +void VCR_GenericRecordString( const char *pEventName, const char *pString ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + VCR_THREADSAFE; + VCR_Event( VCREvent_GenericString ); + + if ( g_VCRMode != VCR_Record ) + Error( "VCR_GenericRecordString( %s ): not recording a VCR file", pEventName ); + + // Write the event name (or 255 if none). + WriteShortString( pEventName ); + WriteShortString( pString ); +} + + +void VCR_GenericPlaybackString( const char *pEventName, const char *pString ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() || g_VCRMode != VCR_Playback ) + Error( "VCR_GenericPlaybackString( %s ): not playing back a VCR file", pEventName ); + + VCR_THREADSAFE; + VCR_Event( VCREvent_GenericString ); + + ReadAndVerifyShortString( pEventName ); + ReadAndVerifyShortString( pString ); +} + + +void VCR_GenericString( const char *pEventName, const char *pString ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return; + + if ( !pEventName ) + pEventName = ""; + + if ( !pString ) + pString = ""; + + if ( g_VCRMode == VCR_Record ) + VCR_GenericRecordString( pEventName, pString ); + else if ( g_VCRMode == VCR_Playback ) + VCR_GenericPlaybackString( pEventName, pString ); +} + + +double VCR_GetPercentCompleted() +{ + if ( g_VCRMode == VCR_Playback ) + { + return (double)g_CurFilePos / g_FileLen; + } + else + { + return 0; + } +} + +void* VCR_CreateThread( + void *lpThreadAttributes, + unsigned long dwStackSize, + void *lpStartAddress, + void *lpParameter, + unsigned long dwCreationFlags, + unsigned long *lpThreadID ) +{ + unsigned dwThreadID = 0; + + // Use _beginthreadex because it sets up C runtime + // correctly, and is safer than _beginthread. See MSDN. + + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + { + if ( g_VCRMode == VCR_Disabled ) + { + HANDLE hThread = (void *)_beginthreadex( + (LPSECURITY_ATTRIBUTES)lpThreadAttributes, + dwStackSize, + (unsigned (__stdcall *) (void *))lpStartAddress, + lpParameter, + dwCreationFlags, + &dwThreadID ); + + if ( lpThreadID ) + *lpThreadID = dwThreadID; + + return hThread; + } + else + { + Error( "VCR_CreateThread: VCR mode disabled in calling thread." ); + } + } + + // We could make this work without too much pain. + if ( GetCurrentThreadId() != g_VCRMainThreadID ) + { + Error( "VCR_CreateThread called outside main thread." ); + } + + if ( g_nVCRThreads >= MAX_VCR_THREADS ) + { + // This is easy to fix if we ever hit it.. just allow more threads. + Error( "VCR_CreateThread: g_nVCRThreads >= MAX_VCR_THREADS." ); + } + + // Write out the VCR event saying this thread is being created. + VCR_THREADSAFE; + VCR_Event( VCREvent_CreateThread ); + + // Create the thread. + HANDLE hThread = (void*)_beginthreadex( + (LPSECURITY_ATTRIBUTES)lpThreadAttributes, + dwStackSize, + (unsigned (__stdcall *) (void *))lpStartAddress, + lpParameter, + dwCreationFlags | CREATE_SUSPENDED, + &dwThreadID ); + + if ( lpThreadID ) + *lpThreadID = dwThreadID; + + if ( !hThread ) + { + // We don't handle this case in VCR mode (but we could pretty easily). + if ( g_VCRMode == VCR_Playback || g_VCRMode == VCR_Record ) + Error( "VCR_CreateThread: CreateThread() failed." ); + + return NULL; + } + + // Register this thread so we can write its ID into future VCR events. + int iNewThread = g_nVCRThreads++; + g_pVCRThreads[iNewThread].m_ThreadID = dwThreadID; + g_pVCRThreads[iNewThread].m_hWaitEvent = CreateEvent( NULL, false, false, NULL ); + g_pVCRThreads[iNewThread].m_bEnabled = true; + + // Now resume the thread. + if ( !( dwCreationFlags & CREATE_SUSPENDED ) ) + { + ResumeThread( hThread ); + } + + return hThread; +} + + +unsigned long VCR_WaitForSingleObject( + void *handle, + unsigned long dwMilliseconds ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return Wrap_WaitForSingleObject( handle, dwMilliseconds ); + //Error( "VCR_WaitForSingleObject: VCR mode disabled in calling thread." ); + + // We have to do the wait here BEFORE we acquire the VCR mutex, otherwise, we could freeze + // the thread that's supposed to signal "handle". + unsigned long ret = 0; + if ( g_VCRMode == VCR_Record ) + { + ret = Wrap_WaitForSingleObject( handle, dwMilliseconds ); + } + + VCR_THREADSAFE; + VCR_Event( VCREvent_WaitForSingleObject ); + + char val = 1; + if ( g_VCRMode == VCR_Record ) + { + if ( ret == WAIT_ABANDONED ) + val = 2; + else if ( ret == WAIT_TIMEOUT ) + val = 3; + + VCR_WriteVal( val ); + return ret; + } + else + { + Assert( g_VCRMode == VCR_Playback ); + + VCR_ReadVal( val ); + if ( val == 1 ) + { + // Hack job.. let other threads start reading events now.. we're basically saying here that we're + // finished reading our VCR event. If we didn't pass the buck onto the next one, if the event hadn't + // already been signalled, it might never get signalled. + vcrThreadSafe.SignalNextEvent(); + + // If it wrote 1, then we know that this call has to signal the object, so just wait until it gets signalled. + ret = Wrap_WaitForSingleObject( handle, INFINITE ); + if ( ret == WAIT_ABANDONED || ret == WAIT_TIMEOUT ) + { + Error( "VCR_WaitForSingleObject: got inconsistent value on playback." ); + } + + return ret; + } + else + { + // Return whatever the function returned while it was recording. + return (val == 2) ? WAIT_ABANDONED : WAIT_TIMEOUT; + } + } +} + +unsigned long VCR_WaitForMultipleObjects( uint32 nHandles, const void **pHandles, int bWaitAll, uint32 timeout ) +{ + // Preamble. + if ( !IsVCRModeEnabledForThisThread() ) + return Wrap_WaitForMultipleObjects( nHandles, pHandles, bWaitAll, timeout ); + + // TODO: + AssertMsg( 0, "Need to implement VCR_WaitForMultipleObjects" ); + return 0; +} + +void VCR_EnterCriticalSection( void *pInputCS ) +{ + CRITICAL_SECTION *pCS = (CRITICAL_SECTION*)pInputCS; + + if ( !IsVCRModeEnabledForThisThread() ) + { + Wrap_EnterCriticalSection( pCS ); + return; + } + + // While recording, let's get the critical section first. + if ( g_VCRMode == VCR_Record ) + { + Wrap_EnterCriticalSection( pCS ); + } + + VCR_THREADSAFE; + VCR_Event( VCREvent_EnterCriticalSection ); + + if ( g_VCRMode == VCR_Playback ) + { + // When playing back, we want to grab the CS -after- the event has been read out, because it means that + // we're the only thread that is at this spot now. If we tried to grab the CS before calling VCR_Event, + // then it might let the wrong thread have the CS on playback. + Wrap_EnterCriticalSection( pCS ); + } +} + + +// ---------------------------------------------------------------------- // +// The global VCR interface. +// ---------------------------------------------------------------------- // + +VCR_t g_VCR = +{ + VCR_Start, + VCR_End, + VCR_GetVCRTraceInterface, + VCR_GetMode, + VCR_SetEnabled, + VCR_SyncToken, + VCR_Hook_Sys_FloatTime, + VCR_Hook_PeekMessage, + VCR_Hook_RecordGameMsg, + VCR_Hook_RecordEndGameMsg, + VCR_Hook_PlaybackGameMsg, + VCR_Hook_recvfrom, + VCR_Hook_GetCursorPos, + VCR_Hook_ScreenToClient, + VCR_Hook_Cmd_Exec, + VCR_Hook_GetCommandLine, + VCR_Hook_RegOpenKeyEx, + VCR_Hook_RegSetValueEx, + VCR_Hook_RegQueryValueEx, + VCR_Hook_RegCreateKeyEx, + VCR_Hook_RegCloseKey, + VCR_Hook_GetNumberOfConsoleInputEvents, + VCR_Hook_ReadConsoleInput, + VCR_Hook_LocalTime, + VCR_Hook_GetKeyState, + VCR_Hook_recv, + VCR_Hook_send, + VCR_GenericRecord, + VCR_GenericPlayback, + VCR_GenericValue, + VCR_GetPercentCompleted, + VCR_CreateThread, + VCR_WaitForSingleObject, + VCR_EnterCriticalSection, + VCR_Hook_Time, + VCR_GenericString, + VCR_GenericValueVerify, + VCR_WaitForMultipleObjects, +}; + +VCR_t *g_pVCR = &g_VCR; + +#endif // NO_VCR \ No newline at end of file diff --git a/tier0/vcrmode_posix.cpp b/tier0/vcrmode_posix.cpp new file mode 100644 index 0000000..9e0003e --- /dev/null +++ b/tier0/vcrmode_posix.cpp @@ -0,0 +1,970 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "tier0/threadtools.h" + +#define PROTECTED_THINGS_DISABLE +#include "tier0/vcrmode.h" +#include "tier0/dbg.h" +#include "extendedtrace.h" + +// FIXME: We totally have a bad tier dependency here +#include "inputsystem/InputEnums.h" + +#define VCRFILE_VERSION 2 + +#define VCR_RuntimeAssert(x) VCR_RuntimeAssertFn(x, #x) +#define PvAlloc malloc + +bool g_bExpectingWindowProcCalls = false; + +IVCRHelpers *g_pHelpers = 0; + +FILE *g_pVCRFile = NULL; +VCRMode_t g_VCRMode = VCR_Disabled; + +VCRMode_t g_OldVCRMode = (VCRMode_t)-1; // Stored temporarily between SetEnabled(0)/SetEnabled(1) blocks. +int g_iCurEvent = 0; + +int g_CurFilePos = 0; // So it knows when we're done playing back. +int g_FileLen = 0; + +VCREvent g_LastReadEvent = (VCREvent)-1; // Last VCR_ReadEvent() call. + +int g_bVCREnabled = 0; + + +// ---------------------------------------------------------------------- // +// Internal functions. +// ---------------------------------------------------------------------- // +static void VCR_Error( const char *pFormat, ... ) +{ +#if defined( _DEBUG ) + DebuggerBreak(); +#endif + char str[256]; + va_list marker; + va_start( marker, pFormat ); + _snprintf( str, sizeof( str ), pFormat, marker ); + va_end( marker ); + + g_pHelpers->ErrorMessage( str ); + VCREnd(); +} + +static void VCR_RuntimeAssertFn(int bAssert, char const *pStr) +{ + if(!bAssert) + { + VCR_Error( "*** VCR ASSERT FAILED: %s ***\n", pStr ); + } +} + +static void VCR_Read(void *pDest, int size) +{ + if(!g_pVCRFile) + { + memset(pDest, 0, size); + return; + } + + fread(pDest, 1, size, g_pVCRFile); + + g_CurFilePos += size; + + VCR_RuntimeAssert(g_CurFilePos <= g_FileLen); + + if(g_CurFilePos >= g_FileLen) + { + VCREnd(); + } +} + +template +static void VCR_ReadVal(T &val) +{ + VCR_Read(&val, sizeof(val)); +} + +static void VCR_Write(void const *pSrc, int size) +{ + fwrite(pSrc, 1, size, g_pVCRFile); + fflush(g_pVCRFile); +} + +template +static void VCR_WriteVal(T &val) +{ + VCR_Write(&val, sizeof(val)); +} + + +// Hook from ExtendedTrace.cpp +bool g_bTraceRead = false; +void OutputDebugStringFormat( const char *pMsg, ... ) +{ + char msg[4096]; + va_list marker; + va_start( marker, pMsg ); + _vsnprintf( msg, sizeof( msg )-1, pMsg, marker ); + va_end( marker ); + int len = strlen( msg ); + + if ( g_bTraceRead ) + { + char tempData[4096]; + int tempLen; + VCR_ReadVal( tempLen ); + VCR_RuntimeAssert( tempLen <= sizeof( tempData ) ); + VCR_Read( tempData, tempLen ); + tempData[tempLen] = 0; + fprintf( stderr, "FILE: " ); + fprintf( stderr, "%s", tempData ); + + VCR_RuntimeAssert( memcmp( msg, tempData, len ) == 0 ); + } + else + { + VCR_WriteVal( len ); + VCR_Write( msg, len ); + } +} + + +static VCREvent VCR_ReadEvent() +{ + g_bTraceRead = true; + //STACKTRACE(); + + char event; + VCR_Read(&event, 1); + g_LastReadEvent = (VCREvent)event; + + return (VCREvent)event; +} + + +static void VCR_WriteEvent(VCREvent event) +{ + g_bTraceRead = false; + //STACKTRACE(); + + // Write a stack trace. + char cEvent = (char)event; + VCR_Write(&cEvent, 1); +} + +static void VCR_IncrementEvent() +{ + ++g_iCurEvent; +} + +static void VCR_Event(VCREvent type) +{ + if(g_VCRMode == VCR_Disabled) + return; + + VCR_IncrementEvent(); + if(g_VCRMode == VCR_Record) + { + VCR_WriteEvent(type); + } + else + { + VCREvent currentEvent = VCR_ReadEvent(); + VCR_RuntimeAssert( currentEvent == type ); + } +} + + +// ---------------------------------------------------------------------- // +// VCR trace interface. +// ---------------------------------------------------------------------- // + +class CVCRTrace : public IVCRTrace +{ +public: + virtual VCREvent ReadEvent() + { + return VCR_ReadEvent(); + } + + virtual void Read( void *pDest, int size ) + { + VCR_Read( pDest, size ); + } +}; + +static CVCRTrace g_VCRTrace; + + +// ---------------------------------------------------------------------- // +// VCR interface. +// ---------------------------------------------------------------------- // + +static int VCR_Start( char const *pFilename, bool bRecord, IVCRHelpers *pHelpers ) +{ + unsigned long version; + + g_pHelpers = pHelpers; + + VCREnd(); + + EXTENDEDTRACEINITIALIZE( "/tmp/hl2" ); + + g_OldVCRMode = (VCRMode_t)-1; + if(bRecord) + { + g_pVCRFile = fopen( pFilename, "wb" ); + if( g_pVCRFile ) + { + // Write the version. + version = VCRFILE_VERSION; + VCR_Write(&version, sizeof(version)); + + g_VCRMode = VCR_Record; + return true; + } + else + { + return false; + } + } + else + { + g_pVCRFile = fopen( pFilename, "rb" ); + if( g_pVCRFile ) + { + // Get the file length. + fseek(g_pVCRFile, 0, SEEK_END); + g_FileLen = ftell(g_pVCRFile); + fseek(g_pVCRFile, 0, SEEK_SET); + g_CurFilePos = 0; + + // Verify the file version. + VCR_Read(&version, sizeof(version)); + if(version != VCRFILE_VERSION) + { + assert(!"VCR_Start: invalid file version"); + VCREnd(); + return FALSE; + } + + g_VCRMode = VCR_Playback; + return TRUE; + } + else + { + return FALSE; + } + } +} + + +static void VCR_End() +{ + if(g_pVCRFile) + { + fclose(g_pVCRFile); + g_pVCRFile = NULL; + } + + g_VCRMode = VCR_Disabled; + EXTENDEDTRACEUNINITIALIZE(); +} + + +static IVCRTrace* VCR_GetVCRTraceInterface() +{ + return &g_VCRTrace; +} + + +static VCRMode_t VCR_GetMode() +{ + return g_VCRMode; +} + + +static void VCR_SetEnabled(int bEnabled) +{ + if(bEnabled) + { + VCR_RuntimeAssert(g_OldVCRMode != (VCRMode_t)-1); + g_VCRMode = g_OldVCRMode; + g_OldVCRMode = (VCRMode_t)-1; + } + else + { + VCR_RuntimeAssert(g_OldVCRMode == (VCRMode_t)-1); + g_OldVCRMode = g_VCRMode; + g_VCRMode = VCR_Disabled; + } +} + + +static void VCR_SyncToken(char const *pToken) +{ + unsigned char len; + + VCR_Event(VCREvent_SyncToken); + + if(g_VCRMode == VCR_Record) + { + int intLen = strlen( pToken ); + assert( intLen <= 255 ); + + len = (unsigned char)intLen; + + VCR_Write(&len, 1); + VCR_Write(pToken, len); + } + else if(g_VCRMode == VCR_Playback) + { + char test[256]; + + VCR_Read(&len, 1); + VCR_Read(test, len); + + VCR_RuntimeAssert( len == (unsigned char)strlen(pToken) ); + VCR_RuntimeAssert( memcmp(pToken, test, len) == 0 ); + } +} + + +static double VCR_Hook_Sys_FloatTime(double time) +{ + VCR_Event(VCREvent_Sys_FloatTime); + + if(g_VCRMode == VCR_Record) + { + VCR_Write(&time, sizeof(time)); + } + else if(g_VCRMode == VCR_Playback) + { + VCR_Read(&time, sizeof(time)); + } + + return time; +} + + + +static int VCR_Hook_PeekMessage( + struct tagMSG *msg, + void *hWnd, + unsigned int wMsgFilterMin, + unsigned int wMsgFilterMax, + unsigned int wRemoveMsg + ) +{ + Assert( "VCR_Hook_PeekMessage unsupported" ); + return 0; +} + + +void VCR_Hook_RecordGameMsg( const InputEvent_t& event ) +{ + if ( g_VCRMode == VCR_Record ) + { + VCR_Event( VCREvent_GameMsg ); + + char val = 1; + VCR_WriteVal( val ); + VCR_WriteVal( event.m_nType ); + VCR_WriteVal( event.m_nData ); + VCR_WriteVal( event.m_nData2 ); + VCR_WriteVal( event.m_nData3 ); + } +} + + +void VCR_Hook_RecordEndGameMsg() +{ + if ( g_VCRMode == VCR_Record ) + { + VCR_Event( VCREvent_GameMsg ); + char val = 0; + VCR_WriteVal( val ); // record that there are no more messages. + } +} + + +bool VCR_Hook_PlaybackGameMsg( InputEvent_t* pEvent ) +{ + if ( g_VCRMode == VCR_Playback ) + { + VCR_Event( VCREvent_GameMsg ); + + char bMsg; + VCR_ReadVal( bMsg ); + if ( bMsg ) + { + VCR_ReadVal( pEvent->m_nType ); + VCR_ReadVal( pEvent->m_nData ); + VCR_ReadVal( pEvent->m_nData2 ); + VCR_ReadVal( pEvent->m_nData3 ); + return true; + } + } + + return false; +} + + + +static void VCR_Hook_GetCursorPos(struct tagPOINT *pt) +{ + Assert( "VCR_Hook_GetCursorPos unsupported" ); +} + + +static void VCR_Hook_ScreenToClient(void *hWnd, struct tagPOINT *pt) +{ + Assert( "VCR_Hook_GetCursorPos unsupported" ); +} + + +static int VCR_Hook_recvfrom(int s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen) +{ + VCR_Event(VCREvent_recvfrom); + + int ret; + if ( g_VCRMode == VCR_Playback ) + { + // Get the result from our file. + VCR_Read(&ret, sizeof(ret)); + if(ret == -1) + { + int err; + VCR_ReadVal(err); + errno = err; + } + else + { + VCR_Read( buf, ret ); + + char bFrom; + VCR_ReadVal( bFrom ); + if ( bFrom ) + { + VCR_Read( from, *fromlen ); + } + } + } + else + { + ret = recvfrom(s, buf, len, flags, from, (socklen_t *)(fromlen)); + + if ( g_VCRMode == VCR_Record ) + { + // Record the result. + VCR_Write(&ret, sizeof(ret)); + if(ret == -1) + { + VCR_WriteVal(errno); + } + else + { + VCR_Write( buf, ret ); + + char bFrom = !!from; + VCR_WriteVal( bFrom ); + if ( bFrom ) + VCR_Write( from, *fromlen ); + } + } + } + + return ret; +} + + +static void VCR_Hook_Cmd_Exec(char **f) +{ + VCR_Event(VCREvent_Cmd_Exec); + + if(g_VCRMode == VCR_Playback) + { + int len; + + VCR_Read(&len, sizeof(len)); + if(len == -1) + { + *f = NULL; + } + else + { + *f = (char*)PvAlloc(len); + VCR_Read(*f, len); + } + } + else if(g_VCRMode == VCR_Record) + { + int len; + char *str = *f; + + if(str) + { + len = strlen(str)+1; + VCR_Write(&len, sizeof(len)); + VCR_Write(str, len); + } + else + { + len = -1; + VCR_Write(&len, sizeof(len)); + } + } +} + +#define MAX_LINUX_CMDLINE 512 +static char linuxCmdline[ MAX_LINUX_CMDLINE +7 ]; // room for -steam + +const char * BuildCmdLine( int argc, char **argv, bool fAddSteam ) +{ + int len; + int i; + + for (len = 0, i = 0; i < argc; i++) + { + len += strlen(argv[i]); + } + + if ( len > MAX_LINUX_CMDLINE ) + { + printf( "command line too long, %i max\n", MAX_LINUX_CMDLINE ); + exit(-1); + return ""; + } + + linuxCmdline[0] = '\0'; + for ( i = 0; i < argc; i++ ) + { + if ( i > 0 ) + { + strcat( linuxCmdline, " " ); + } + strcat( linuxCmdline, argv[ i ] ); + } + + if( fAddSteam ) + { + strcat( linuxCmdline, " -steam" ); + } + + return linuxCmdline; +} + +char *GetCommandLine() +{ + return linuxCmdline; +} + + +static char* VCR_Hook_GetCommandLine() +{ + VCR_Event(VCREvent_CmdLine); + + int len; + char *ret; + + if(g_VCRMode == VCR_Playback) + { + VCR_Read(&len, sizeof(len)); + ret = new char[len]; + VCR_Read(ret, len); + } + else + { + ret = GetCommandLine(); + + if(g_VCRMode == VCR_Record) + { + len = strlen(ret) + 1; + VCR_WriteVal(len); + VCR_Write(ret, len); + } + } + + return ret; +} + + +static long VCR_Hook_RegOpenKeyEx( void *hKey, const char *lpSubKey, unsigned long ulOptions, unsigned long samDesired, void *pHKey ) +{ + Assert( "VCR_Hook_RegOpenKeyEx unsupported" ); + return 0; +} + + +static long VCR_Hook_RegSetValueEx(void *hKey, char const *lpValueName, unsigned long Reserved, unsigned long dwType, unsigned char const *lpData, unsigned long cbData) +{ + Assert( "VCR_Hook_RegSetValueEx unsupported" ); + return 0; +} + + +static long VCR_Hook_RegQueryValueEx(void *hKey, char const *lpValueName, unsigned long *lpReserved, unsigned long *lpType, unsigned char *lpData, unsigned long *lpcbData) +{ + Assert( "VCR_Hook_RegQueryValueEx unsupported" ); + return 0; +} + + +static long VCR_Hook_RegCreateKeyEx(void *hKey, char const *lpSubKey, unsigned long Reserved, char *lpClass, unsigned long dwOptions, + unsigned long samDesired, void *lpSecurityAttributes, void *phkResult, unsigned long *lpdwDisposition) +{ + Assert( "VCR_Hook_RegCreateKeyEx unsupported" ); + return 0; +} + + +static void VCR_Hook_RegCloseKey(void *hKey) +{ + Assert( "VCR_Hook_RegCloseKey unsupported" ); +} + + +int VCR_Hook_GetNumberOfConsoleInputEvents( void *hInput, unsigned long *pNumEvents ) +{ + VCR_Event( VCREvent_GetNumberOfConsoleInputEvents ); + + char ret; + if ( g_VCRMode == VCR_Playback ) + { + VCR_ReadVal( ret ); + VCR_ReadVal( *pNumEvents ); + } + else + { + ret = 1; + + if ( g_VCRMode == VCR_Record ) + { + VCR_WriteVal( ret ); + VCR_WriteVal( *pNumEvents ); + } + } + + return ret; +} + + +int VCR_Hook_ReadConsoleInput( void *hInput, void *pRecs, int nMaxRecs, unsigned long *pNumRead ) +{ + Assert( "VCR_Hook_ReadConsoleInput unsupported" ); + return 0; +} + + +void VCR_Hook_LocalTime( struct tm *today ) +{ + // We just provide a wrapper on this function so we can protect access to time() everywhere. + time_t ltime; + time( <ime ); + tm *pTime = localtime( <ime ); + memcpy( today, pTime, sizeof( *today ) ); +} + + +short VCR_Hook_GetKeyState( int nVirtKey ) +{ + Assert( "VCREvent_GetKeyState unsupported" ); + return 0; +} + +void VCR_GenericRecord( const char *pEventName, const void *pData, int len ) +{ + VCR_Event( VCREvent_Generic ); + + if ( g_VCRMode != VCR_Record ) + Error( "VCR_GenericRecord( %s ): not recording a VCR file", pEventName ); + + // Write the event name (or 255 if none). + int nameLen = 255; + if ( pEventName ) + { + nameLen = strlen( pEventName ) + 1; + if ( nameLen >= 255 ) + { + VCR_Error( "VCR_GenericRecord( %s ): nameLen too long (%d)", pEventName, nameLen ); + return; + } + } + unsigned char ucNameLen = (unsigned char)nameLen; + VCR_WriteVal( ucNameLen ); + VCR_Write( pEventName, ucNameLen ); + + // Write the data. + VCR_WriteVal( len ); + VCR_Write( pData, len ); +} + + +int VCR_GenericPlayback( const char *pEventName, void *pOutData, int maxLen, bool bForceSameLen ) +{ + VCR_Event( VCREvent_Generic ); + + if ( g_VCRMode != VCR_Playback ) + Error( "VCR_Playback( %s ): not playing back a VCR file", pEventName ); + + unsigned char nameLen; + VCR_ReadVal( nameLen ); + if ( nameLen != 255 ) + { + char testName[512]; + VCR_Read( testName, nameLen ); + if ( strcmp( pEventName, testName ) != 0 ) + { + VCR_Error( "VCR_GenericPlayback( %s ) - event name does not match '%s'", pEventName, testName ); + return 0; + } + } + + int dataLen; + VCR_ReadVal( dataLen ); + if ( dataLen > maxLen ) + { + VCR_Error( "VCR_GenericPlayback( %s ) - generic data too long (greater than maxLen: %d)", pEventName, maxLen ); + return 0; + } + else if ( bForceSameLen && dataLen != maxLen ) + { + VCR_Error( "VCR_GenericPlayback( %s ) - data size in file (%d) different than desired (%d)", pEventName, dataLen, maxLen ); + return 0; + } + + VCR_Read( pOutData, dataLen ); + return dataLen; +} + + +void VCR_GenericValue( const char *pEventName, void *pData, int maxLen ) +{ + if ( g_VCRMode == VCR_Record ) + VCR_GenericRecord( pEventName, pData, maxLen ); + else if ( g_VCRMode == VCR_Playback ) + VCR_GenericPlayback( pEventName, pData, maxLen, true ); +} + + +static int VCR_Hook_recv(int s, char *buf, int len, int flags) +{ + VCR_Event(VCREvent_recv); + + int ret; + if ( g_VCRMode == VCR_Playback ) + { + // Get the result from our file. + VCR_Read(&ret, sizeof(ret)); + if(ret == -1) + { + int err; + VCR_ReadVal(err); + errno = err; + } + else + { + VCR_Read( buf, ret ); + } + } + else + { + ret = recv( s, buf, len, flags ); + + if ( g_VCRMode == VCR_Record ) + { + // Record the result. + VCR_Write(&ret, sizeof(ret)); + if(ret == -1) + { + VCR_WriteVal(errno); + } + else + { + VCR_Write( buf, ret ); + } + } + } + + return ret; +} + +static int VCR_Hook_send(int s, const char *buf, int len, int flags) +{ + VCR_Event(VCREvent_send); + + int ret; + if ( g_VCRMode == VCR_Playback ) + { + // Get the result from our file. + VCR_Read(&ret, sizeof(ret)); + if(ret == -1) + { + int err; + VCR_ReadVal(err); + errno = err; + } + } + else + { + ret = send( s, buf, len, flags ); + + if ( g_VCRMode == VCR_Record ) + { + // Record the result. + VCR_Write(&ret, sizeof(ret)); + if(ret == -1) + { + VCR_WriteVal(errno); + } + } + } + + return ret; +} + + + +double VCR_GetPercentCompleted() +{ + if ( g_VCRMode == VCR_Playback ) + { + return (double)g_CurFilePos / g_FileLen; + } + else + { + return 0; + } +} + +void* VCR_CreateThread( + void *lpThreadAttributes, + unsigned long dwStackSize, + void *lpStartAddress, + void *lpParameter, + unsigned long dwCreationFlags, + unsigned long *lpThreadID ) +{ + return CreateSimpleThread( (ThreadFunc_t)lpStartAddress, lpParameter, lpThreadID, 0 ); +} + + + +unsigned long VCR_WaitForSingleObject( + void *handle, + unsigned long dwMilliseconds ) +{ + return -1; +} + +unsigned long VCR_WaitForMultipleObjects( uint32 nHandles, const void **pHandles, int bWaitAll, uint32 timeout ) +{ + return -1; +} + +void VCR_EnterCriticalSection( void *pInputCS ) +{ +} + + +void VCR_Hook_Time( long *today ) +{ + // We just provide a wrapper on this function so we can protect access to time() everywhere. + // NOTE: For 64-bit systems we should eventually get a function that takes a time_t, but we should have + // until about 2038 to do that before we overflow a long. + time_t curTime; + time( &curTime ); + + VCR_Event( VCREvent_Time ); + if ( g_VCRMode == VCR_Playback ) + { + VCR_Read( &curTime, sizeof( curTime ) ); + } + else if ( g_VCRMode == VCR_Record ) + { + VCR_Write( &curTime, sizeof( curTime ) ); + } + + *today = (long)curTime; +} + + + +void VCR_GenericString( const char *pEventName, const char *pString ) +{ +} + + +void VCR_GenericValueVerify( const tchar *pEventName, const void *pData, int maxLen ) +{ +} + + + +// ---------------------------------------------------------------------- // +// The global VCR interface. +// ---------------------------------------------------------------------- // + +VCR_t g_VCR = +{ + VCR_Start, + VCR_End, + VCR_GetVCRTraceInterface, + VCR_GetMode, + VCR_SetEnabled, + VCR_SyncToken, + VCR_Hook_Sys_FloatTime, + VCR_Hook_PeekMessage, + VCR_Hook_RecordGameMsg, + VCR_Hook_RecordEndGameMsg, + VCR_Hook_PlaybackGameMsg, + VCR_Hook_recvfrom, + VCR_Hook_GetCursorPos, + VCR_Hook_ScreenToClient, + VCR_Hook_Cmd_Exec, + VCR_Hook_GetCommandLine, + VCR_Hook_RegOpenKeyEx, + VCR_Hook_RegSetValueEx, + VCR_Hook_RegQueryValueEx, + VCR_Hook_RegCreateKeyEx, + VCR_Hook_RegCloseKey, + VCR_Hook_GetNumberOfConsoleInputEvents, + VCR_Hook_ReadConsoleInput, + VCR_Hook_LocalTime, + VCR_Hook_GetKeyState, + VCR_Hook_recv, + VCR_Hook_send, + VCR_GenericRecord, + VCR_GenericPlayback, + VCR_GenericValue, + + VCR_GetPercentCompleted, + VCR_CreateThread, + VCR_WaitForSingleObject, + VCR_EnterCriticalSection, + VCR_Hook_Time, + VCR_GenericString, + VCR_GenericValueVerify, + VCR_WaitForMultipleObjects, + +}; + +VCR_t *g_pVCR = &g_VCR; + + diff --git a/tier0/vcrmode_xbox.cpp b/tier0/vcrmode_xbox.cpp new file mode 100644 index 0000000..905832e --- /dev/null +++ b/tier0/vcrmode_xbox.cpp @@ -0,0 +1,290 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: VCR mode records a client's game and allows you to +// play it back and reproduce it exactly. When playing it back, nothing +// is simulated on the server, but all server packets are recorded. +// +// Most of the VCR mode functionality is accomplished through hooks +// called at various points in the engine. +// +// $NoKeywords: $ +//============================================================================= + +#include "xbox/xbox_platform.h" +#include "xbox/xbox_win32stubs.h" +#include +#include +#include +#include +#include +#include "tier0/vcrmode.h" +#include "tier0/dbg.h" + +#define VCRFILE_VERSION 2 +#define VCR_RuntimeAssert(x) VCR_RuntimeAssertFn(x, #x) + +// ---------------------------------------------------------------------- // +// Internal functions. +// ---------------------------------------------------------------------- // + +static void VCR_Error( const char *pFormat, ... ) +{ +} + +static void VCR_RuntimeAssertFn(int bAssert, char const *pStr) +{ +} + +static void VCR_Read(void *pDest, int size) +{ +} + +template +static void VCR_ReadVal(T &val) +{ + VCR_Read(&val, sizeof(val)); +} + +static void VCR_Write(void const *pSrc, int size) +{ +} + +template +static void VCR_WriteVal(T &val) +{ +} + +// Hook from ExtendedTrace.cpp +bool g_bTraceRead = false; +void OutputDebugStringFormat( const char *pMsg, ... ) +{ +} + +static VCREvent VCR_ReadEvent() +{ + return (VCREvent)-1; +} + +static void VCR_WriteEvent(VCREvent event) +{ +} + +static void VCR_IncrementEvent() +{ +} + +static void VCR_Event(VCREvent type) +{ +} + +// ---------------------------------------------------------------------- // +// VCR trace interface. +// ---------------------------------------------------------------------- // + +class CVCRTrace : public IVCRTrace +{ +public: + virtual VCREvent ReadEvent() + { + return VCR_ReadEvent(); + } + + virtual void Read( void *pDest, int size ) + { + VCR_Read( pDest, size ); + } +}; + +static CVCRTrace g_VCRTrace; + + +// ---------------------------------------------------------------------- // +// VCR interface. +// ---------------------------------------------------------------------- // + +static int VCR_Start( char const *pFilename, bool bRecord, IVCRHelpers *pHelpers ) +{ + return 0; +} + +static void VCR_End() +{ +} + +static IVCRTrace* VCR_GetVCRTraceInterface() +{ + return 0; +} + +static VCRMode VCR_GetMode() +{ + return VCR_Disabled; +} + +static void VCR_SetEnabled(int bEnabled) +{ +} + +static void VCR_SyncToken(char const *pToken) +{ +} + +static double VCR_Hook_Sys_FloatTime(double time) +{ + return 0; +} + +static int VCR_Hook_PeekMessage( + struct tagMSG *msg, + void *hWnd, + unsigned int wMsgFilterMin, + unsigned int wMsgFilterMax, + unsigned int wRemoveMsg + ) +{ + return 0; +} + +void VCR_Hook_RecordGameMsg( unsigned int uMsg, unsigned int wParam, long lParam ) +{ +} + +void VCR_Hook_RecordEndGameMsg() +{ +} + +bool VCR_Hook_PlaybackGameMsg( unsigned int &uMsg, unsigned int &wParam, long &lParam ) +{ + return 0; +} + +static void VCR_Hook_GetCursorPos(struct tagPOINT *pt) +{ +} + +static void VCR_Hook_ScreenToClient(void *hWnd, struct tagPOINT *pt) +{ +} + +static int VCR_Hook_recvfrom(int s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen) +{ + return 0; +} + +static int VCR_Hook_recv(int s, char *buf, int len, int flags) +{ + return 0; +} + +static int VCR_Hook_send(int s, const char *buf, int len, int flags) +{ + return 0; +} + +static void VCR_Hook_Cmd_Exec(char **f) +{ +} + +static char* VCR_Hook_GetCommandLine() +{ + return GetCommandLine(); +} + +static long VCR_Hook_RegOpenKeyEx( void *hKey, const char *lpSubKey, unsigned long ulOptions, unsigned long samDesired, void *pHKey ) +{ + return 0; +} + +static long VCR_Hook_RegSetValueEx(void *hKey, char const *lpValueName, unsigned long Reserved, unsigned long dwType, unsigned char const *lpData, unsigned long cbData) +{ + return 0; +} + +static long VCR_Hook_RegQueryValueEx(void *hKey, char const *lpValueName, unsigned long *lpReserved, unsigned long *lpType, unsigned char *lpData, unsigned long *lpcbData) +{ + return 0; +} + +static long VCR_Hook_RegCreateKeyEx(void *hKey, char const *lpSubKey, unsigned long Reserved, char *lpClass, unsigned long dwOptions, + unsigned long samDesired, void *lpSecurityAttributes, void *phkResult, unsigned long *lpdwDisposition) +{ + return 0; +} + +static void VCR_Hook_RegCloseKey(void *hKey) +{ +} + +int VCR_Hook_GetNumberOfConsoleInputEvents( void *hInput, unsigned long *pNumEvents ) +{ + return 0; +} + +int VCR_Hook_ReadConsoleInput( void *hInput, void *pRecs, int nMaxRecs, unsigned long *pNumRead ) +{ + return 0; +} + +void VCR_Hook_LocalTime( struct tm *today ) +{ +} + +short VCR_Hook_GetKeyState( int nVirtKey ) +{ + return 0; +} + +void VCR_GenericRecord( const char *pEventName, const void *pData, int len ) +{ +} + +int VCR_GenericPlayback( const char *pEventName, void *pOutData, int maxLen, bool bForceSameLen ) +{ + return 0; +} + +void VCR_GenericValue( const char *pEventName, void *pData, int maxLen ) +{ +} + +// ---------------------------------------------------------------------- // +// The global VCR interface. +// ---------------------------------------------------------------------- // + +VCR_t g_VCR = +{ + VCR_Start, + VCR_End, + VCR_GetVCRTraceInterface, + VCR_GetMode, + VCR_SetEnabled, + VCR_SyncToken, + VCR_Hook_Sys_FloatTime, + VCR_Hook_PeekMessage, + VCR_Hook_RecordGameMsg, + VCR_Hook_RecordEndGameMsg, + VCR_Hook_PlaybackGameMsg, + VCR_Hook_recvfrom, + VCR_Hook_GetCursorPos, + VCR_Hook_ScreenToClient, + VCR_Hook_Cmd_Exec, + VCR_Hook_GetCommandLine, + VCR_Hook_RegOpenKeyEx, + VCR_Hook_RegSetValueEx, + VCR_Hook_RegQueryValueEx, + VCR_Hook_RegCreateKeyEx, + VCR_Hook_RegCloseKey, + VCR_Hook_GetNumberOfConsoleInputEvents, + VCR_Hook_ReadConsoleInput, + VCR_Hook_LocalTime, + VCR_Hook_GetKeyState, + VCR_Hook_recv, + VCR_Hook_send, + VCR_GenericRecord, + VCR_GenericPlayback, + VCR_GenericValue +}; + +VCR_t *g_pVCR = &g_VCR; + + diff --git a/tier0/vprof.cpp b/tier0/vprof.cpp new file mode 100644 index 0000000..0ef667c --- /dev/null +++ b/tier0/vprof.cpp @@ -0,0 +1,2094 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Real-Time Hierarchical Profiling +// +// $NoKeywords: $ +//===========================================================================// + +#include "pch_tier0.h" + +#include "tier0/memalloc.h" +#include "tier0/valve_off.h" + +#if defined(_WIN32) && !defined(_X360) +#define WIN_32_LEAN_AND_MEAN +#include +#endif + +#include + +#ifdef _WIN32 +#pragma warning(disable:4073) +#pragma init_seg( lib ) +#endif + +#pragma warning(push, 1) +#pragma warning(disable:4786) +#pragma warning(disable:4530) +#include +#include +#include +#pragma warning(pop) + +#include "tier0/valve_on.h" +#include "tier0/vprof.h" +#include "tier0/l2cache.h" +#include "tier0/tslist.h" +#include "tier0/dynfunction.h" + +#ifdef _X360 + +#include "xbox/xbox_console.h" + +#else // NOT _X360: + +#include "tier0/memdbgon.h" + +#endif + +// NOTE: Explicitly and intentionally using STL in here to not generate any +// cyclical dependencies between the low-level debug library and the higher +// level data structures (toml 01-27-03) +using namespace std; + +#ifdef VPROF_ENABLED + + +#if defined(_X360) && !defined(_CERT) // enable PIX CPU trace: +#include "tracerecording.h" +#pragma comment( lib, "tracerecording.lib" ) +#pragma comment( lib, "xbdm.lib" ) +#endif + +//----------------------------------------------------------------------------- +bool g_VProfSignalSpike; + +//----------------------------------------------------------------------------- + +CVProfile g_VProfCurrentProfile; + +int CVProfNode::s_iCurrentUniqueNodeID = 0; + +CVProfNode::~CVProfNode() +{ +#if !defined( _WIN32 ) && !defined( POSIX ) + delete m_pChild; + delete m_pSibling; +#endif +} + +CVProfNode *CVProfNode::GetSubNode( const tchar *pszName, int detailLevel, const tchar *pBudgetGroupName, int budgetFlags ) +{ + // Try to find this sub node + CVProfNode * child = m_pChild; + while ( child ) + { + if ( child->m_pszName == pszName ) + { + return child; + } + child = child->m_pSibling; + } + + // We didn't find it, so add it + CVProfNode * node = new CVProfNode( pszName, detailLevel, this, pBudgetGroupName, budgetFlags ); + node->m_pSibling = m_pChild; + m_pChild = node; + return node; +} + +CVProfNode *CVProfNode::GetSubNode( const tchar *pszName, int detailLevel, const tchar *pBudgetGroupName ) +{ + return GetSubNode( pszName, detailLevel, pBudgetGroupName, BUDGETFLAG_OTHER ); +} + +//------------------------------------- + +void CVProfNode::EnterScope() +{ + m_nCurFrameCalls++; + if ( m_nRecursions++ == 0 ) + { + m_Timer.Start(); +#ifndef _X360 + if ( g_VProfCurrentProfile.UsePME() ) + { + m_L2Cache.Start(); + } +#else // 360 code: + if ( g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0) ) + { + m_PMCData.Start(); + } + + if ( (m_iBitFlags & kCPUTrace) != 0) + { + // this node is to be recorded. Which recording mode are we in? + switch ( g_VProfCurrentProfile.GetCPUTraceMode() ) + { + case CVProfile::kFirstHitNode: + case CVProfile::kAllNodesInFrame_Recording: + case CVProfile::kAllNodesInFrame_RecordingMultiFrame: + // we are presently recording. + if ( !XTraceStartRecording( g_VProfCurrentProfile.GetCPUTraceFilename() ) ) + { + Msg( "XTraceStartRecording failed, error code %d\n", GetLastError() ); + } + + default: + // no default. + break; + } + + } + +#endif + +#ifdef VPROF_VTUNE_GROUP + g_VProfCurrentProfile.PushGroup( m_BudgetGroupID ); +#endif + } +} + +//------------------------------------- + +bool CVProfNode::ExitScope() +{ + if ( --m_nRecursions == 0 && m_nCurFrameCalls != 0 ) + { + m_Timer.End(); + m_CurFrameTime += m_Timer.GetDuration(); +#ifndef _X360 + if ( g_VProfCurrentProfile.UsePME() ) + { + m_L2Cache.End(); + m_iCurL2CacheMiss += m_L2Cache.GetL2CacheMisses(); + } +#else // 360 code: + if ( g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0) ) + { + m_PMCData.End(); + m_iCurL2CacheMiss += m_PMCData.GetL2CacheMisses(); + m_iCurLoadHitStores += m_PMCData.GetLHS(); + } + + if ( (m_iBitFlags & kCPUTrace) != 0 ) + { + // this node is enabled to be recorded. What mode are we in? + switch ( g_VProfCurrentProfile.GetCPUTraceMode() ) + { + case CVProfile::kFirstHitNode: + { + // one-off recording. stop now. + if ( XTraceStopRecording() ) + { + Msg( "CPU trace finished.\n" ); + if ( g_VProfCurrentProfile.TraceCompleteEvent() ) + { + // signal VXConsole that trace is completed + XBX_rTraceComplete(); + } + } + // don't trace again next frame, overwriting the file. + g_VProfCurrentProfile.SetCPUTraceEnabled( CVProfile::kDisabled ); + break; + } + + case CVProfile::kAllNodesInFrame_Recording: + case CVProfile::kAllNodesInFrame_RecordingMultiFrame: + { + // one-off recording. stop now. + if ( XTraceStopRecording() ) + { + if ( g_VProfCurrentProfile.GetCPUTraceMode() == CVProfile::kAllNodesInFrame_RecordingMultiFrame ) + { + Msg( "%.3f msec in %s\n", m_CurFrameTime.GetMillisecondsF(), g_VProfCurrentProfile.GetCPUTraceFilename() ); + } + else + { + Msg( "CPU trace finished.\n" ); + } + } + + // Spew time info for file to allow figuring it out later + g_VProfCurrentProfile.LatchMultiFrame( m_CurFrameTime.GetLongCycles() ); + +#if 0 // This doesn't want to work on the xbox360-- MoveFile not available or file still being put down to disk? + char suffix[ 32 ]; + _snprintf( suffix, sizeof( suffix ), "_%.3f_msecs", flMsecs ); + + char fn[ 512 ]; + + strncpy( fn, g_VProfCurrentProfile.GetCPUTraceFilename(), sizeof( fn ) ); + + char *p = strrchr( fn, '.' ); + if ( *p ) + { + *p = 0; + } + strncat( fn, suffix, sizeof( fn ) ); + strncat( fn, ".pix2", sizeof( fn ) ); + + BOOL bSuccess = MoveFile( g_VProfCurrentProfile.GetCPUTraceFilename(), fn ); + if ( !bSuccess ) + { + DWORD eCode = GetLastError(); + Msg( "Error %d\n", eCode ); + } +#endif + + // we're still recording until the frame is done. + // but, increment the index. + g_VProfCurrentProfile.IncrementMultiTraceIndex(); + break; + } + + } + + // g_VProfCurrentProfile.IsCPUTraceEnabled() && + + + } + +#endif + +#ifdef VPROF_VTUNE_GROUP + g_VProfCurrentProfile.PopGroup(); +#endif + } + return ( m_nRecursions == 0 ); +} + +//------------------------------------- + +void CVProfNode::Pause() +{ + if ( m_nRecursions > 0 ) + { + m_Timer.End(); + m_CurFrameTime += m_Timer.GetDuration(); + +#ifndef _X360 + if ( g_VProfCurrentProfile.UsePME() ) + { + m_L2Cache.End(); + m_iCurL2CacheMiss += m_L2Cache.GetL2CacheMisses(); + } +#else // 360 code: + if ( g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0) ) + { + m_PMCData.End(); + m_iCurL2CacheMiss += m_PMCData.GetL2CacheMisses(); + m_iCurLoadHitStores += m_PMCData.GetLHS(); + } +#endif + } + if ( m_pChild ) + { + m_pChild->Pause(); + } + if ( m_pSibling ) + { + m_pSibling->Pause(); + } +} + +//------------------------------------- + +void CVProfNode::Resume() +{ + if ( m_nRecursions > 0 ) + { + m_Timer.Start(); + +#ifndef _X360 + if ( g_VProfCurrentProfile.UsePME() ) + { + m_L2Cache.Start(); + } +#else + if ( g_VProfCurrentProfile.UsePME() || ((m_iBitFlags & kRecordL2) != 0) ) + { + m_PMCData.Start(); + } +#endif + } + if ( m_pChild ) + { + m_pChild->Resume(); + } + if ( m_pSibling ) + { + m_pSibling->Resume(); + } +} + +//------------------------------------- + +void CVProfNode::Reset() +{ + m_nPrevFrameCalls = 0; + m_PrevFrameTime.Init(); + + m_nCurFrameCalls = 0; + m_CurFrameTime.Init(); + + m_nTotalCalls = 0; + m_TotalTime.Init(); + + m_PeakTime.Init(); + + m_iPrevL2CacheMiss = 0; + m_iCurL2CacheMiss = 0; + m_iTotalL2CacheMiss = 0; + +#ifdef _X360 + m_iPrevLoadHitStores = 0; + m_iCurLoadHitStores = 0; + m_iTotalLoadHitStores = 0; +#endif + + if ( m_pChild ) + { + m_pChild->Reset(); + } + if ( m_pSibling ) + { + m_pSibling->Reset(); + } +} + + +//------------------------------------- + +void CVProfNode::MarkFrame() +{ + m_nPrevFrameCalls = m_nCurFrameCalls; + m_PrevFrameTime = m_CurFrameTime; + m_iPrevL2CacheMiss = m_iCurL2CacheMiss; +#ifdef _X360 + m_iPrevLoadHitStores = m_iCurLoadHitStores; +#endif + m_nTotalCalls += m_nCurFrameCalls; + m_TotalTime += m_CurFrameTime; + + if ( m_PeakTime.IsLessThan( m_CurFrameTime ) ) + { + m_PeakTime = m_CurFrameTime; + } + + m_CurFrameTime.Init(); + m_nCurFrameCalls = 0; + m_iTotalL2CacheMiss += m_iCurL2CacheMiss; + m_iCurL2CacheMiss = 0; +#ifdef _X360 + m_iTotalLoadHitStores += m_iCurLoadHitStores; + m_iCurLoadHitStores = 0; +#endif + if ( m_pChild ) + { + m_pChild->MarkFrame(); + } + if ( m_pSibling ) + { + m_pSibling->MarkFrame(); + } +} + +//------------------------------------- + +void CVProfNode::ResetPeak() +{ + m_PeakTime.Init(); + + if ( m_pChild ) + { + m_pChild->ResetPeak(); + } + if ( m_pSibling ) + { + m_pSibling->ResetPeak(); + } +} + + +void CVProfNode::SetCurFrameTime( unsigned long milliseconds ) +{ + m_CurFrameTime.Init( (float)milliseconds ); +} +#ifdef DBGFLAG_VALIDATE +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void CVProfNode::Validate( CValidator &validator, tchar *pchName ) +{ + validator.Push( _T("CVProfNode"), this, pchName ); + + m_L2Cache.Validate( validator, _T("m_L2Cache") ); + + if ( m_pSibling ) + m_pSibling->Validate( validator, _T("m_pSibling") ); + if ( m_pChild ) + m_pChild->Validate( validator, _T("m_pChild") ); + + validator.Pop( ); +} +#endif // DBGFLAG_VALIDATE + + + +//----------------------------------------------------------------------------- + +struct TimeSums_t +{ + const tchar *pszProfileScope; + int calls; + double time; + double timeLessChildren; + double peak; +}; + +static bool TimeCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + return ( lhs.time > rhs.time ); +} + +static bool TimeLessChildrenCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + return ( lhs.timeLessChildren > rhs.timeLessChildren ); +} + +static bool PeakCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + return ( lhs.peak > rhs.peak ); +} + +static bool AverageTimeCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + double avgLhs = ( lhs.calls ) ? lhs.time / (double)lhs.calls : 0.0; + double avgRhs = ( rhs.calls ) ? rhs.time / (double)rhs.calls : 0.0; + return ( avgLhs > avgRhs ); +} + +static bool AverageTimeLessChildrenCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + double avgLhs = ( lhs.calls ) ? lhs.timeLessChildren / (double)lhs.calls : 0.0; + double avgRhs = ( rhs.calls ) ? rhs.timeLessChildren / (double)rhs.calls : 0.0; + return ( avgLhs > avgRhs ); + +} +static bool PeakOverAverageCompare( const TimeSums_t &lhs, const TimeSums_t &rhs ) +{ + double avgLhs = ( lhs.calls ) ? lhs.time / (double)lhs.calls : 0.0; + double avgRhs = ( rhs.calls ) ? rhs.time / (double)rhs.calls : 0.0; + + double lhsPoA = ( avgLhs != 0 ) ? lhs.peak / avgLhs : 0.0; + double rhsPoA = ( avgRhs != 0 ) ? rhs.peak / avgRhs : 0.0; + + return ( lhsPoA > rhsPoA ); +} + +map g_TimesLessChildren; +int g_TotalFrames; +map g_TimeSumsMap; +vector g_TimeSums; +CVProfNode * g_pStartNode; +const tchar * g_pszSumNode; + +//------------------------------------- + +void CVProfile::SumTimes( CVProfNode *pNode, int budgetGroupID ) +{ + if ( !pNode ) + return; // this generally only happens on a failed FindNode() + + bool bSetStartNode; + if ( !g_pStartNode && _tcscmp( pNode->GetName(), g_pszSumNode ) == 0 ) + { + g_pStartNode = pNode; + bSetStartNode = true; + } + else + bSetStartNode = false; + + if ( GetRoot() != pNode ) + { + if ( g_pStartNode && pNode->GetTotalCalls() > 0 && ( budgetGroupID == -1 || pNode->GetBudgetGroupID() == budgetGroupID ) ) + { + double timeLessChildren = pNode->GetTotalTimeLessChildren(); + + g_TimesLessChildren.insert( make_pair( pNode, timeLessChildren ) ); + + map::iterator iter; + iter = g_TimeSumsMap.find( pNode->GetName() ); // intenionally using address of string rather than string compare (toml 01-27-03) + if ( iter == g_TimeSumsMap.end() ) + { + TimeSums_t timeSums = { pNode->GetName(), pNode->GetTotalCalls(), pNode->GetTotalTime(), timeLessChildren, pNode->GetPeakTime() }; + g_TimeSumsMap.insert( make_pair( pNode->GetName(), g_TimeSums.size() ) ); + g_TimeSums.push_back( timeSums ); + } + else + { + TimeSums_t &timeSums = g_TimeSums[iter->second]; + timeSums.calls += pNode->GetTotalCalls(); + timeSums.time += pNode->GetTotalTime(); + timeSums.timeLessChildren += timeLessChildren; + if ( pNode->GetPeakTime() > timeSums.peak ) + timeSums.peak = pNode->GetPeakTime(); + } + } + + if( ( !g_pStartNode || pNode != g_pStartNode ) && pNode->GetSibling() ) + { + SumTimes( pNode->GetSibling(), budgetGroupID ); + } + } + + if( pNode->GetChild() ) + { + SumTimes( pNode->GetChild(), budgetGroupID ); + } + + if ( bSetStartNode ) + g_pStartNode = NULL; +} + +//------------------------------------- + +CVProfNode *CVProfile::FindNode( CVProfNode *pStartNode, const tchar *pszNode ) +{ + if ( _tcscmp( pStartNode->GetName(), pszNode ) != 0 ) + { + CVProfNode *pFoundNode = NULL; + if ( pStartNode->GetSibling() ) + { + pFoundNode = FindNode( pStartNode->GetSibling(), pszNode ); + } + + if ( !pFoundNode && pStartNode->GetChild() ) + { + pFoundNode = FindNode( pStartNode->GetChild(), pszNode ); + } + + return pFoundNode; + } + return pStartNode; +} + +//------------------------------------- +#ifdef _X360 + +void CVProfile::PMCDisableAllNodes(CVProfNode *pStartNode) +{ + if (pStartNode == NULL) + { + pStartNode = GetRoot(); + } + + pStartNode->EnableL2andLHS(false); + + if ( pStartNode->GetSibling() ) + { + PMCDisableAllNodes(pStartNode->GetSibling()); + } + + if ( pStartNode->GetChild() ) + { + PMCDisableAllNodes(pStartNode->GetChild()); + } +} + +// recursively set l2/lhs recording state for a node and all children AND SIBLINGS +static void PMCRecursiveL2Set(CVProfNode *pNode, bool enableState) +{ + if ( pNode ) + { + pNode->EnableL2andLHS(enableState); + if ( pNode->GetSibling() ) + { + PMCRecursiveL2Set( pNode->GetSibling(), enableState ); + } + if ( pNode->GetChild() ) + { + PMCRecursiveL2Set( pNode->GetChild(), enableState ); + } + } +} + +bool CVProfile::PMCEnableL2Upon(const tchar *pszNodeName, bool bRecursive) +{ + // PMCDisableAllNodes(); + CVProfNode *pNode = FindNode( GetRoot(), pszNodeName ); + if (pNode) + { + pNode->EnableL2andLHS(true); + if (bRecursive) + { + PMCRecursiveL2Set(pNode->GetChild(), true); + } + return true; + } + else + { + return false; + } +} + +bool CVProfile::PMCDisableL2Upon(const tchar *pszNodeName, bool bRecursive) +{ + // PMCDisableAllNodes(); + CVProfNode *pNode = FindNode( GetRoot(), pszNodeName ); + if ( pNode ) + { + pNode->EnableL2andLHS( false ); + if ( bRecursive ) + { + PMCRecursiveL2Set( pNode->GetChild(), false ); + } + return true; + } + else + { + return false; + } +} + +static void DumpEnabledPMCNodesInner(CVProfNode* pNode) +{ + if (!pNode) + return; + + if (pNode->IsL2andLHSEnabled()) + { + Msg( _T("\t%s\n"), pNode->GetName() ); + } + + // depth first printing clearer + if ( pNode->GetChild() ) + { + DumpEnabledPMCNodesInner(pNode->GetChild()); + } + + if ( pNode->GetSibling() ) + { + DumpEnabledPMCNodesInner(pNode->GetChild()); + } +} + +void CVProfile::DumpEnabledPMCNodes( void ) +{ + Msg( _T("Nodes enabled for PMC counters:\n") ); + CVProfNode *pNode = GetRoot(); + DumpEnabledPMCNodesInner( pNode ); + + Msg( _T("(end)\n") ); +} + +CVProfNode *CVProfile::CPUTraceGetEnabledNode(CVProfNode *pStartNode) +{ + if (!pStartNode) + { + pStartNode = GetRoot(); + } + + if ( (pStartNode->m_iBitFlags & CVProfNode::kCPUTrace) != 0 ) + { + return pStartNode; + } + + if (pStartNode->GetSibling()) + { + CVProfNode *retval = CPUTraceGetEnabledNode(pStartNode->GetSibling()); + if (retval) + return retval; + } + + if (pStartNode->GetChild()) + { + CVProfNode *retval = CPUTraceGetEnabledNode(pStartNode->GetChild()); + if (retval) + return retval; + } + + return NULL; +} + +const char *CVProfile::SetCPUTraceFilename( const char *filename ) +{ + strncpy( m_CPUTraceFilename, filename, sizeof( m_CPUTraceFilename ) ); + return GetCPUTraceFilename(); +} + +/// Returns a pointer to an internal static, so you don't need to +/// make temporary char buffers for this to write into. What of it? +/// You're not hanging on to that pointer. That would be foolish. +const char *CVProfile::GetCPUTraceFilename() +{ + static char retBuf[256]; + + switch ( m_iCPUTraceEnabled ) + { + case kAllNodesInFrame_WaitingForMark: + case kAllNodesInFrame_Recording: + _snprintf( retBuf, sizeof( retBuf ), "e:\\%.128s%.4d.pix2", m_CPUTraceFilename, m_iSuccessiveTraceIndex ); + break; + + case kAllNodesInFrame_WaitingForMarkMultiFrame: + case kAllNodesInFrame_RecordingMultiFrame: + _snprintf( retBuf, sizeof( retBuf ), "e:\\%.128s_%.4d_%.4d.pix2", m_CPUTraceFilename, m_nFrameCount, m_iSuccessiveTraceIndex ); + break; + + default: + _snprintf( retBuf, sizeof( retBuf ), "e:\\%.128s.pix2", m_CPUTraceFilename ); + } + + return retBuf; +} + +bool CVProfile::TraceCompleteEvent( void ) +{ + return m_bTraceCompleteEvent; +} + +CVProfNode *CVProfile::CPUTraceEnableForNode(const tchar *pszNodeName) +{ + // disable whatever may be enabled already (we can only trace one node at a time) + CPUTraceDisableAllNodes(); + + CVProfNode *which = FindNode(GetRoot(), pszNodeName); + if (which) + { + which->m_iBitFlags |= CVProfNode::kCPUTrace; + return which; + } + else + return NULL; +} + +void CVProfile::CPUTraceDisableAllNodes(CVProfNode *pStartNode) +{ + if (!pStartNode) + { + pStartNode = GetRoot(); + } + + pStartNode->m_iBitFlags &= ~CVProfNode::kCPUTrace; + + if (pStartNode->GetSibling()) + { + CPUTraceDisableAllNodes(pStartNode->GetSibling()); + } + + if (pStartNode->GetChild()) + { + CPUTraceDisableAllNodes(pStartNode->GetChild()); + } + +} + +#endif + +//------------------------------------- + +void CVProfile::SumTimes( const tchar *pszStartNode, int budgetGroupID ) +{ + if ( GetRoot()->GetChild() ) + { + if ( pszStartNode == NULL ) + g_pStartNode = GetRoot(); + else + g_pStartNode = NULL; + + g_pszSumNode = pszStartNode; + SumTimes( GetRoot(), budgetGroupID ); + g_pStartNode = NULL; + } + +} + +//------------------------------------- + +// This array lets us generate the commonly used indent levels +// without looping. That then lets us print our vprof nodes +// in a single call, which is more efficient and works better +// with output streams like ETW where each call represents a +// 'line' of text. Indent levels beyond what is represented +// in this array are, regretfully, clamped, however the highest +// indent level seen in testing was 10. +static const char* s_indentText[] = +{ + "", // 0 + "", // 1 + "| ", // 2 + "| | ", // 3 + "| | | ", // 4 + "| | | | ", // 5 + "| | | | | ", // 6 + "| | | | | | ", // 7 + "| | | | | | | ", // 8 + "| | | | | | | | ", // 9 + "| | | | | | | | | ", // 10 + "| | | | | | | | | | ", // 11 + "| | | | | | | | | | | ", // 12 + "| | | | | | | | | | | | ", // 13 + "| | | | | | | | | | | | | ", // 14 +}; + +void CVProfile::DumpNodes( CVProfNode *pNode, int indent, bool bAverageAndCountOnly ) +{ + if ( !pNode ) + return; // this generally only happens on a failed FindNode() + + bool fIsRoot = ( pNode == GetRoot() ); + + if ( fIsRoot || pNode == g_pStartNode ) + { + if( bAverageAndCountOnly ) + { + m_pOutputStream( _T(" Avg Time/Frame (ms)\n") ); + m_pOutputStream( _T("[ func+child func ] Count\n") ); + m_pOutputStream( _T(" ---------- ------ ------\n") ); + } + else + { + m_pOutputStream( _T(" Sum (ms) Avg Time/Frame (ms) Avg Time/Call (ms)\n") ); + m_pOutputStream( _T("[ func+child func ] [ func+child func ] [ func+child func ] Count Peak\n") ); + m_pOutputStream( _T(" ---------- ------ ---------- ------ ---------- ------ ------ ------\n") ); + } + } + + if ( !fIsRoot ) + { + map::iterator iterTimeLessChildren = g_TimesLessChildren.find( pNode ); + + indent = Max( indent, 0 ); + indent = Min( indent, (int)ARRAYSIZE( s_indentText ) - 1 ); + const char* indentText = s_indentText[ indent ]; + double dNodeTime = 0; + if(iterTimeLessChildren != g_TimesLessChildren.end()) + dNodeTime = iterTimeLessChildren->second; + + if( bAverageAndCountOnly ) + { + m_pOutputStream( _T(" %10.3f %6.2f %6d %s%s\n"), + ( pNode->GetTotalCalls() > 0 ) ? pNode->GetTotalTime() / (double)NumFramesSampled() : 0, + ( pNode->GetTotalCalls() > 0 ) ? dNodeTime / (double)NumFramesSampled() : 0, + pNode->GetTotalCalls(), indentText, pNode->GetName() ); + } + else + { + m_pOutputStream( _T(" %10.3f %6.2f %10.3f %6.2f %10.3f %6.2f %6d %6.2f %s%s\n"), + pNode->GetTotalTime(), dNodeTime, + ( pNode->GetTotalCalls() > 0 ) ? pNode->GetTotalTime() / (double)NumFramesSampled() : 0, + ( pNode->GetTotalCalls() > 0 ) ? dNodeTime / (double)NumFramesSampled() : 0, + ( pNode->GetTotalCalls() > 0 ) ? pNode->GetTotalTime() / (double)pNode->GetTotalCalls() : 0, + ( pNode->GetTotalCalls() > 0 ) ? dNodeTime / (double)pNode->GetTotalCalls() : 0, + pNode->GetTotalCalls(), pNode->GetPeakTime(), indentText, pNode->GetName() ); + } + } + + if( pNode->GetChild() ) + { + DumpNodes( pNode->GetChild(), indent + 1, bAverageAndCountOnly ); + } + + if( !( fIsRoot || pNode == g_pStartNode ) && pNode->GetSibling() ) + { + DumpNodes( pNode->GetSibling(), indent, bAverageAndCountOnly ); + } +} + +//------------------------------------- + +#if defined( _X360 ) +static void CalcBudgetGroupTimes_Recursive( CVProfNode *pNode, unsigned int *groupTimes, int numGroups, float flScale ) +{ + int groupID; + CVProfNode *nodePtr; + + groupID = pNode->GetBudgetGroupID(); + if ( groupID >= numGroups ) + { + return; + } + + groupTimes[groupID] += flScale*pNode->GetPrevTimeLessChildren(); + + nodePtr = pNode->GetSibling(); + if ( nodePtr ) + { + CalcBudgetGroupTimes_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } + + nodePtr = pNode->GetChild(); + if ( nodePtr ) + { + CalcBudgetGroupTimes_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } +} + +static void CalcBudgetGroupL2CacheMisses_Recursive( CVProfNode *pNode, unsigned int *groupTimes, int numGroups, float flScale ) +{ + int groupID; + CVProfNode *nodePtr; + + groupID = pNode->GetBudgetGroupID(); + if ( groupID >= numGroups ) + { + return; + } + + groupTimes[groupID] += flScale*pNode->GetPrevL2CacheMissLessChildren(); + + nodePtr = pNode->GetSibling(); + if ( nodePtr ) + { + CalcBudgetGroupL2CacheMisses_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } + + nodePtr = pNode->GetChild(); + if ( nodePtr ) + { + CalcBudgetGroupL2CacheMisses_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } +} + +static void CalcBudgetGroupLHS_Recursive( CVProfNode *pNode, unsigned int *groupTimes, int numGroups, float flScale ) +{ + int groupID; + CVProfNode *nodePtr; + + groupID = pNode->GetBudgetGroupID(); + if ( groupID >= numGroups ) + { + return; + } + + groupTimes[groupID] += flScale*pNode->GetPrevLoadHitStoreLessChildren(); + + nodePtr = pNode->GetSibling(); + if ( nodePtr ) + { + CalcBudgetGroupLHS_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } + + nodePtr = pNode->GetChild(); + if ( nodePtr ) + { + CalcBudgetGroupLHS_Recursive( nodePtr, groupTimes, numGroups, flScale ); + } +} + + +void CVProfile::VXConsoleReportMode( VXConsoleReportMode_t mode ) +{ + m_ReportMode = mode; +} + +void CVProfile::VXConsoleReportScale( VXConsoleReportMode_t mode, float flScale ) +{ + m_pReportScale[mode] = flScale; +} + + +//----------------------------------------------------------------------------- +// Send the all the counter attributes once to VXConsole at profiling start +//----------------------------------------------------------------------------- +void CVProfile::VXProfileStart() +{ + const char *names[XBX_MAX_PROFILE_COUNTERS]; + unsigned int colors[XBX_MAX_PROFILE_COUNTERS]; + int numGroups; + int counterGroup; + const char *pGroupName; + int i; + int r,g,b,a; + + // vprof system must be running + if ( m_enabled <= 0 || !m_UpdateMode ) + { + return; + } + + if ( m_UpdateMode & VPROF_UPDATE_BUDGET ) + { + // update budget profiling + numGroups = g_VProfCurrentProfile.GetNumBudgetGroups(); + if ( numGroups > XBX_MAX_PROFILE_COUNTERS ) + { + numGroups = XBX_MAX_PROFILE_COUNTERS; + } + for ( i=0; i XBX_MAX_PROFILE_COUNTERS ) + { + numGroups = XBX_MAX_PROFILE_COUNTERS; + } + memset( groupData, 0, numGroups * sizeof( unsigned int ) ); + + CVProfNode *pNode = g_VProfCurrentProfile.GetRoot(); + if ( pNode && pNode->GetChild() ) + { + switch ( m_ReportMode ) + { + default: + case VXCONSOLE_REPORT_TIME: + CalcBudgetGroupTimes_Recursive( pNode->GetChild(), groupData, numGroups, m_pReportScale[VXCONSOLE_REPORT_TIME] ); + break; + + case VXCONSOLE_REPORT_L2CACHE_MISSES: + CalcBudgetGroupL2CacheMisses_Recursive( pNode->GetChild(), groupData, numGroups, m_pReportScale[VXCONSOLE_REPORT_L2CACHE_MISSES] ); + break; + + case VXCONSOLE_REPORT_LOAD_HIT_STORE: + CalcBudgetGroupLHS_Recursive( pNode->GetChild(), groupData, numGroups, m_pReportScale[VXCONSOLE_REPORT_LOAD_HIT_STORE] ); + break; + } + } + + XBX_rSetProfileData( "cpu", numGroups, groupData ); + } + + if ( m_UpdateMode & ( VPROF_UPDATE_TEXTURE_GLOBAL|VPROF_UPDATE_TEXTURE_PERFRAME ) ) + { + // send the texture counters + numGroups = 0; + counterGroup = ( m_UpdateMode & VPROF_UPDATE_TEXTURE_GLOBAL ) ? COUNTER_GROUP_TEXTURE_GLOBAL : COUNTER_GROUP_TEXTURE_PER_FRAME; + for ( i = 0; i < g_VProfCurrentProfile.GetNumCounters(); i++ ) + { + if ( g_VProfCurrentProfile.GetCounterGroup( i ) == counterGroup ) + { + // get the size in bytes + groupData[numGroups++] = g_VProfCurrentProfile.GetCounterValue( i ); + if ( numGroups == XBX_MAX_PROFILE_COUNTERS ) + { + break; + } + } + } + + XBX_rSetProfileData( "texture", numGroups, groupData ); + } +} + +void CVProfile::VXEnableUpdateMode( int event, bool bEnable ) +{ + // enable or disable the updating of specified events + if ( bEnable ) + { + m_UpdateMode |= event; + } + else + { + m_UpdateMode &= ~event; + } + + // force a resend of possibly affected attributes + VXProfileStart(); +} + +#define MAX_VPROF_NODES_IN_LIST 4096 +static void VXBuildNodeList_r( CVProfNode *pNode, xVProfNodeItem_t *pNodeList, int *pNumNodes ) +{ + if ( !pNode ) + { + return; + } + if ( *pNumNodes >= MAX_VPROF_NODES_IN_LIST ) + { + // list full + return; + } + + // add to list + pNodeList[*pNumNodes].pName = (const char *)pNode->GetName(); + + pNodeList[*pNumNodes].pBudgetGroupName = g_VProfCurrentProfile.GetBudgetGroupName( pNode->GetBudgetGroupID() ); + int r, g, b, a; + g_VProfCurrentProfile.GetBudgetGroupColor( pNode->GetBudgetGroupID(), r, g, b, a ); + pNodeList[*pNumNodes].budgetGroupColor = XMAKECOLOR( r, g, b ); + + pNodeList[*pNumNodes].totalCalls = pNode->GetTotalCalls(); + pNodeList[*pNumNodes].inclusiveTime = pNode->GetTotalTime(); + pNodeList[*pNumNodes].exclusiveTime = pNode->GetTotalTimeLessChildren(); + (*pNumNodes)++; + + CVProfNode *nodePtr = pNode->GetSibling(); + if ( nodePtr ) + { + VXBuildNodeList_r( nodePtr, pNodeList, pNumNodes ); + } + + nodePtr = pNode->GetChild(); + if ( nodePtr ) + { + VXBuildNodeList_r( nodePtr, pNodeList, pNumNodes ); + } +} +void CVProfile::VXSendNodes( void ) +{ + Pause(); + + xVProfNodeItem_t *pNodeList = (xVProfNodeItem_t *)stackalloc( MAX_VPROF_NODES_IN_LIST * sizeof(xVProfNodeItem_t) ); + int numNodes = 0; + VXBuildNodeList_r( GetRoot(), pNodeList, &numNodes ); + + // send to vxconsole + XBX_rVProfNodeList( numNodes, pNodeList ); + + Resume(); +} +#endif + +//------------------------------------- +static void DumpSorted( CVProfile::StreamOut_t outputStream, const tchar *pszHeading, double totalTime, bool (*pfnSort)( const TimeSums_t &, const TimeSums_t & ), int maxLen = 999999 ) +{ + unsigned i; + vector sortedSums; + sortedSums = g_TimeSums; + sort( sortedSums.begin(), sortedSums.end(), pfnSort ); + + outputStream( _T("%s\n"), pszHeading); + outputStream( _T(" Scope Calls Calls/Frame Time+Child Pct Time Pct Avg/Frame Avg/Call Avg-NoChild Peak\n")); + outputStream( _T(" ---------------------------------------------------- ----------- ----------- ----------- ------ ----------- ------ ----------- ----------- ----------- -----------\n")); + for ( i = 0; i < sortedSums.size() && i < (unsigned)maxLen; i++ ) + { + double avg = ( sortedSums[i].calls ) ? sortedSums[i].time / (double)sortedSums[i].calls : 0.0; + double avgLessChildren = ( sortedSums[i].calls ) ? sortedSums[i].timeLessChildren / (double)sortedSums[i].calls : 0.0; + + outputStream( _T(" %52.52s%12d%12.3f%12.3f%7.2f%12.3f%7.2f%12.3f%12.3f%12.3f%12.3f\n"), + sortedSums[i].pszProfileScope, + sortedSums[i].calls, + (float)sortedSums[i].calls / (float)g_TotalFrames, + sortedSums[i].time, + min( ( sortedSums[i].time / totalTime ) * 100.0, 100.0 ), + sortedSums[i].timeLessChildren, + min( ( sortedSums[i].timeLessChildren / totalTime ) * 100.0, 100.0 ), + sortedSums[i].time / (float)g_TotalFrames, + avg, + avgLessChildren, + sortedSums[i].peak ); + } +} + +#if _X360 +// Dump information on all nodes with PMC recording +static void DumpPMC( CVProfNode *pNode, bool &bPrintHeader, uint64 L2thresh = 1, uint64 LHSthresh = 1 ) +{ + if (!pNode) return; + + uint64 l2 = pNode->GetL2CacheMisses(); + uint64 lhs = pNode->GetLoadHitStores(); + if ( l2 > L2thresh && + lhs > LHSthresh ) + { + // met threshold. + if (bPrintHeader) + { + // print header + Msg( _T("-- 360 PMC information --\n") ); + Msg( _T("Scope L2/call L2/frame LHS/call LHS/frame\n") ); + Msg( _T("---------------------------------------------------- --------- --------- --------- ---------\n") ); + + bPrintHeader = false; + } + + // print + float calls = pNode->GetTotalCalls(); + float frames = g_TotalFrames; + Msg( _T("%52.52s %9.2f %9.2f %9.2f %9.2f\n"), pNode->GetName(), l2/calls, l2/frames, lhs/calls, lhs/frames ); + } + + if ( pNode->GetSibling() ) + { + DumpPMC( pNode->GetSibling(), bPrintHeader, L2thresh, LHSthresh ); + } + + if ( pNode->GetChild() ) + { + DumpPMC( pNode->GetChild(), bPrintHeader, L2thresh, LHSthresh ); + } +} +#endif + +//------------------------------------- + +void CVProfile::SetOutputStream( StreamOut_t outputStream ) +{ + if ( outputStream != NULL ) + m_pOutputStream = outputStream; + else + m_pOutputStream = Msg; +} + +//------------------------------------- + +void CVProfile::OutputReport( int type, const tchar *pszStartNode, int budgetGroupID ) +{ + m_pOutputStream( _T("******** BEGIN VPROF REPORT ********\n")); +#ifdef _MSC_VER +#if (_MSC_VER < 1300) + m_pOutputStream( _T(" (note: this report exceeds the output capacity of MSVC debug window. Use console window or console log.) \n")); +#endif +#endif + + g_TotalFrames = max( NumFramesSampled() - 1, 1 ); + + if ( NumFramesSampled() == 0 || GetTotalTimeSampled() == 0) + m_pOutputStream( _T("No samples\n") ); + else + { + if ( type & VPRT_SUMMARY ) + { + m_pOutputStream( _T("-- Summary --\n") ); + m_pOutputStream( _T("%d frames sampled for %.2f seconds\n"), g_TotalFrames, GetTotalTimeSampled() / 1000.0 ); + m_pOutputStream( _T("Average %.2f fps, %.2f ms per frame\n"), 1000.0 / ( GetTotalTimeSampled() / g_TotalFrames ), GetTotalTimeSampled() / g_TotalFrames ); + m_pOutputStream( _T("Peak %.2f ms frame\n"), GetPeakFrameTime() ); + + double timeAccountedFor = 100.0 - ( m_Root.GetTotalTimeLessChildren() / m_Root.GetTotalTime() ); + m_pOutputStream( _T("%.0f pct of time accounted for\n"), min( 100.0, timeAccountedFor ) ); + m_pOutputStream( _T("\n") ); + } + + if ( pszStartNode == NULL ) + { + pszStartNode = GetRoot()->GetName(); + } + + SumTimes( pszStartNode, budgetGroupID ); + + // Dump the hierarchy + if ( type & VPRT_HIERARCHY ) + { + m_pOutputStream( _T("-- Hierarchical Call Graph --\n")); + if ( pszStartNode == NULL ) + g_pStartNode = NULL; + else + g_pStartNode = FindNode( GetRoot(), pszStartNode ); + + DumpNodes( (!g_pStartNode) ? GetRoot() : g_pStartNode, 0, false ); + m_pOutputStream( _T("\n") ); + } + + if ( type & VPRT_HIERARCHY_TIME_PER_FRAME_AND_COUNT_ONLY ) + { + m_pOutputStream( _T("-- Hierarchical Call Graph --\n")); + if ( pszStartNode == NULL ) + g_pStartNode = NULL; + else + g_pStartNode = FindNode( GetRoot(), pszStartNode ); + + DumpNodes( (!g_pStartNode) ? GetRoot() : g_pStartNode, 0, true ); + m_pOutputStream( _T("\n") ); + } + + int maxLen = ( type & VPRT_LIST_TOP_ITEMS_ONLY ) ? 25 : 999999; + + if ( type & VPRT_LIST_BY_TIME ) + { + DumpSorted( m_pOutputStream, _T("-- Profile scopes sorted by time (including children) --"), GetTotalTimeSampled(), TimeCompare, maxLen ); + m_pOutputStream( _T("\n") ); + } + if ( type & VPRT_LIST_BY_TIME_LESS_CHILDREN ) + { + DumpSorted( m_pOutputStream, _T("-- Profile scopes sorted by time (without children) --"), GetTotalTimeSampled(), TimeLessChildrenCompare, maxLen ); + m_pOutputStream( _T("\n") ); + } + if ( type & VPRT_LIST_BY_AVG_TIME ) + { + DumpSorted( m_pOutputStream, _T("-- Profile scopes sorted by average time (including children) --"), GetTotalTimeSampled(), AverageTimeCompare, maxLen ); + m_pOutputStream( _T("\n") ); + } + if ( type & VPRT_LIST_BY_AVG_TIME_LESS_CHILDREN ) + { + DumpSorted( m_pOutputStream, _T("-- Profile scopes sorted by average time (without children) --"), GetTotalTimeSampled(), AverageTimeLessChildrenCompare, maxLen ); + m_pOutputStream( _T("\n") ); + } + if ( type & VPRT_LIST_BY_PEAK_TIME ) + { + DumpSorted( m_pOutputStream, _T("-- Profile scopes sorted by peak --"), GetTotalTimeSampled(), PeakCompare, maxLen); + m_pOutputStream( _T("\n") ); + } + if ( type & VPRT_LIST_BY_PEAK_OVER_AVERAGE ) + { + DumpSorted( m_pOutputStream, _T("-- Profile scopes sorted by peak over average (including children) --"), GetTotalTimeSampled(), PeakOverAverageCompare, maxLen ); + m_pOutputStream( _T("\n") ); + } + + // TODO: Functions by time less children + // TODO: Functions by time averages + // TODO: Functions by peak + // TODO: Peak deviation from average + g_TimesLessChildren.clear(); + g_TimeSumsMap.clear(); + g_TimeSums.clear(); + +#ifdef _X360 + bool bPrintedHeader = true; + DumpPMC( FindNode( GetRoot(), pszStartNode ), bPrintedHeader ); +#endif + + } + m_pOutputStream( _T("******** END VPROF REPORT ********\n")); + +} + +//============================================================================= + +CVProfile::CVProfile() + : m_Root( _T("Root"), 0, NULL, VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, 0 ), + m_pCurNode( &m_Root ), + m_nFrames( 0 ), + m_enabled( 0 ), + m_pausedEnabledDepth( 0 ), + m_fAtRoot( true ), + m_pOutputStream( Msg ) +{ +#ifdef VPROF_VTUNE_GROUP + m_GroupIDStackDepth = 1; + m_GroupIDStack[0] = 0; // VPROF_BUDGETGROUP_OTHER_UNACCOUNTED +#endif + + m_TargetThreadId = ThreadGetCurrentId(); + + // Go ahead and allocate 32 slots for budget group names + MEM_ALLOC_CREDIT(); + m_pBudgetGroups = new CVProfile::CBudgetGroup[32]; + m_nBudgetGroupNames = 0; + m_nBudgetGroupNamesAllocated = 32; + + // Add these here so that they will always be in the same order. + // VPROF_BUDGETGROUP_OTHER_UNACCOUNTED has to be FIRST!!!! + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_UNACCOUNTED, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_WORLD_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_DISPLACEMENT_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_GAME, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_PLAYER, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_NPCS, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_SERVER_ANIM, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_CLIENT_ANIMATION, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_PHYSICS, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_STATICPROP_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_MODEL_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_MODEL_FAST_PATH_RENDERING,BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_LIGHTCACHE, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_BRUSHMODEL_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_SHADOW_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_DETAILPROP_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_PARTICLE_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_ROPES, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_DLIGHT_RENDERING, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_NETWORKING, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_SOUND, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_VGUI, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OTHER_FILESYSTEM, BUDGETFLAG_OTHER | BUDGETFLAG_SERVER ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_PREDICTION, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_INTERPOLATION, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_SWAP_BUFFERS, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OCCLUSION, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_OVERLAYS, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_TOOLS, BUDGETFLAG_OTHER | BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_TEXTURE_CACHE, BUDGETFLAG_CLIENT ); + BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_REPLAY, BUDGETFLAG_SERVER ); +// BudgetGroupNameToBudgetGroupID( VPROF_BUDGETGROUP_DISP_HULLTRACES ); + + m_bPMEInit = false; + m_bPMEEnabled = false; + +#ifdef _X360 + m_UpdateMode = 0; + m_iCPUTraceEnabled = kDisabled; + m_bTraceCompleteEvent = false; + m_iSuccessiveTraceIndex = 0; + m_ReportMode = VXCONSOLE_REPORT_TIME; + m_pReportScale[VXCONSOLE_REPORT_TIME] = 1000.0f; + m_pReportScale[VXCONSOLE_REPORT_L2CACHE_MISSES] = 1.0f; + m_pReportScale[VXCONSOLE_REPORT_LOAD_HIT_STORE] = 0.1f; + m_nFrameCount = 0; + m_nFramesRemaining = 1; + m_WorstCycles = 0; + m_WorstTraceFilename[ 0 ] = 0; +#endif +} + + +CVProfile::~CVProfile() +{ + Term(); +} + + +void CVProfile::FreeNodes_R( CVProfNode *pNode ) +{ + CVProfNode *pNext; + for ( CVProfNode *pChild = pNode->GetChild(); pChild; pChild = pNext ) + { + pNext = pChild->GetSibling(); + FreeNodes_R( pChild ); + } + + if ( pNode == GetRoot() ) + { + pNode->m_pChild = NULL; + } + else + { + delete pNode; + } +} + + +void CVProfile::Term() +{ + int i; + for( i = 0; i < m_nBudgetGroupNames; i++ ) + { + delete [] m_pBudgetGroups[i].m_pName; + } + delete m_pBudgetGroups; + m_nBudgetGroupNames = m_nBudgetGroupNamesAllocated = 0; + m_pBudgetGroups = NULL; + + int n; + for( n = 0; n < m_NumCounters; n++ ) + { + delete [] m_CounterNames[n]; + m_CounterNames[n] = NULL; + } + m_NumCounters = 0; + + // Free the nodes. + if ( GetRoot() ) + { + FreeNodes_R( GetRoot() ); + } +} + + +#define COLORMIN 160 +#define COLORMAX 255 + +static int g_ColorLookup[4] = +{ + COLORMIN, + COLORMAX, + COLORMIN+(COLORMAX-COLORMIN)/3, + COLORMIN+((COLORMAX-COLORMIN)*2)/3, +}; + +#define GET_BIT( val, bitnum ) ( ( val >> bitnum ) & 0x1 ) + +void CVProfile::GetBudgetGroupColor( int budgetGroupID, int &r, int &g, int &b, int &a ) +{ + budgetGroupID = budgetGroupID % ( 1 << 6 ); + + int index; + index = GET_BIT( budgetGroupID, 0 ) | ( GET_BIT( budgetGroupID, 5 ) << 1 ); + r = g_ColorLookup[index]; + index = GET_BIT( budgetGroupID, 1 ) | ( GET_BIT( budgetGroupID, 4 ) << 1 ); + g = g_ColorLookup[index]; + index = GET_BIT( budgetGroupID, 2 ) | ( GET_BIT( budgetGroupID, 3 ) << 1 ); + b = g_ColorLookup[index]; + a = 255; +} + +// return -1 if it doesn't exist. +int CVProfile::FindBudgetGroupName( const tchar *pBudgetGroupName ) +{ + int i; + for( i = 0; i < m_nBudgetGroupNames; i++ ) + { + if( _tcsicmp( pBudgetGroupName, m_pBudgetGroups[i].m_pName ) == 0 ) + { + return i; + } + } + return -1; +} + +int CVProfile::AddBudgetGroupName( const tchar *pBudgetGroupName, int budgetFlags ) +{ + MEM_ALLOC_CREDIT(); + tchar *pNewString = new tchar[ _tcslen( pBudgetGroupName ) + 1 ]; + _tcscpy( pNewString, pBudgetGroupName ); + if( m_nBudgetGroupNames + 1 > m_nBudgetGroupNamesAllocated ) + { + m_nBudgetGroupNamesAllocated *= 2; + m_nBudgetGroupNamesAllocated = max( m_nBudgetGroupNames + 6, m_nBudgetGroupNamesAllocated ); + + CBudgetGroup *pNew = new CBudgetGroup[ m_nBudgetGroupNamesAllocated ]; + for ( int i=0; i < m_nBudgetGroupNames; i++ ) + pNew[i] = m_pBudgetGroups[i]; + + delete [] m_pBudgetGroups; + m_pBudgetGroups = pNew; + } + + m_pBudgetGroups[m_nBudgetGroupNames].m_pName = pNewString; + m_pBudgetGroups[m_nBudgetGroupNames].m_BudgetFlags = budgetFlags; + m_nBudgetGroupNames++; + if( m_pNumBudgetGroupsChangedCallBack ) + { + (*m_pNumBudgetGroupsChangedCallBack)(); + } + +#if defined( _X360 ) + // re-start with all the known budgets + VXProfileStart(); +#endif + return m_nBudgetGroupNames - 1; +} + +int CVProfile::BudgetGroupNameToBudgetGroupID( const tchar *pBudgetGroupName, int budgetFlagsToORIn ) +{ + int budgetGroupID = FindBudgetGroupName( pBudgetGroupName ); + if( budgetGroupID == -1 ) + { + budgetGroupID = AddBudgetGroupName( pBudgetGroupName, budgetFlagsToORIn ); + } + else + { + m_pBudgetGroups[budgetGroupID].m_BudgetFlags |= budgetFlagsToORIn; + } + + return budgetGroupID; +} + +int CVProfile::BudgetGroupNameToBudgetGroupID( const tchar *pBudgetGroupName ) +{ + return BudgetGroupNameToBudgetGroupID( pBudgetGroupName, BUDGETFLAG_OTHER ); +} + +int CVProfile::GetNumBudgetGroups( void ) +{ + return m_nBudgetGroupNames; +} + +void CVProfile::RegisterNumBudgetGroupsChangedCallBack( void (*pCallBack)(void) ) +{ + m_pNumBudgetGroupsChangedCallBack = pCallBack; +} + +void CVProfile::HideBudgetGroup( int budgetGroupID, bool bHide ) +{ + if( budgetGroupID != -1 ) + { + if ( bHide ) + m_pBudgetGroups[budgetGroupID].m_BudgetFlags |= BUDGETFLAG_HIDDEN; + else + m_pBudgetGroups[budgetGroupID].m_BudgetFlags &= ~BUDGETFLAG_HIDDEN; + } +} + +int *CVProfile::FindOrCreateCounter( const tchar *pName, CounterGroup_t eCounterGroup ) +{ + Assert( m_NumCounters+1 < MAXCOUNTERS ); + if ( m_NumCounters + 1 >= MAXCOUNTERS || !InTargetThread() ) + { + static int dummy; + return &dummy; + } + int i; + for( i = 0; i < m_NumCounters; i++ ) + { + if( _tcsicmp( m_CounterNames[i], pName ) == 0 ) + { + // found it! + return &m_Counters[i]; + } + } + + // NOTE: These get freed in ~CVProfile. + MEM_ALLOC_CREDIT(); + tchar *pNewName = new tchar[_tcslen( pName ) + 1]; + _tcscpy( pNewName, pName ); + m_Counters[m_NumCounters] = 0; + m_CounterGroups[m_NumCounters] = (char)eCounterGroup; + m_CounterNames[m_NumCounters++] = pNewName; + return &m_Counters[m_NumCounters-1]; +} + +void CVProfile::ResetCounters( CounterGroup_t eCounterGroup ) +{ + int i; + for( i = 0; i < m_NumCounters; i++ ) + { + if ( m_CounterGroups[i] == eCounterGroup ) + m_Counters[i] = 0; + } +} + +int CVProfile::GetNumCounters() const +{ + return m_NumCounters; +} + +const tchar *CVProfile::GetCounterName( int index ) const +{ + Assert( index >= 0 && index < m_NumCounters ); + return m_CounterNames[index]; +} + +int CVProfile::GetCounterValue( int index ) const +{ + Assert( index >= 0 && index < m_NumCounters ); + return m_Counters[index]; +} + +const tchar *CVProfile::GetCounterNameAndValue( int index, int &val ) const +{ + Assert( index >= 0 && index < m_NumCounters ); + val = m_Counters[index]; + return m_CounterNames[index]; +} + +CounterGroup_t CVProfile::GetCounterGroup( int index ) const +{ + Assert( index >= 0 && index < m_NumCounters ); + return (CounterGroup_t)m_CounterGroups[index]; +} + +#ifdef _X360 +void CVProfile::LatchMultiFrame( int64 cycles ) +{ + if ( cycles > m_WorstCycles ) + { + strncpy( m_WorstTraceFilename, GetCPUTraceFilename(), sizeof( m_WorstTraceFilename ) ); + m_WorstCycles = cycles; + } +} + +void CVProfile::SpewWorstMultiFrame() +{ + CCycleCount cc( m_WorstCycles ); + m_pOutputStream( "%s == %.3f msec\n", m_WorstTraceFilename, cc.GetMillisecondsF() ); +} +#endif + +#ifdef DBGFLAG_VALIDATE + +#ifdef _WIN64 +#error the below is presumably broken on 64 bit +#endif // _WIN64 + +const int k_cSTLMapAllocOffset = 4; +#define GET_INTERNAL_MAP_ALLOC_PTR( pMap ) \ + ( * ( (void **) ( ( ( byte * ) ( pMap ) ) + k_cSTLMapAllocOffset ) ) ) +//----------------------------------------------------------------------------- +// Purpose: Ensure that all of our internal structures are consistent, and +// account for all memory that we've allocated. +// Input: validator - Our global validator object +// pchName - Our name (typically a member var in our container) +//----------------------------------------------------------------------------- +void CVProfile::Validate( CValidator &validator, tchar *pchName ) +{ + validator.Push( _T("CVProfile"), this, pchName ); + + m_Root.Validate( validator, _T("m_Root") ); + + for ( int iBudgetGroup=0; iBudgetGroup < m_nBudgetGroupNames; iBudgetGroup++ ) + validator.ClaimMemory( m_pBudgetGroups[iBudgetGroup].m_pName ); + + validator.ClaimMemory( m_pBudgetGroups ); + + // The std template map class allocates memory internally, but offer no way to get + // access to their pointer. Since this is for debug purposes only and the + // std template classes don't change, just look at the well-known offset. This + // is arguably sick and wrong, kind of like marrying a squirrel. + validator.ClaimMemory( GET_INTERNAL_MAP_ALLOC_PTR( &g_TimesLessChildren ) ); + validator.ClaimMemory( GET_INTERNAL_MAP_ALLOC_PTR( &g_TimeSumsMap ) ); + + validator.Pop( ); +} + +#endif // DBGFLAG_VALIDATE + +#endif // VPROF_ENABLED + +#ifdef RAD_TELEMETRY_ENABLED + +TelemetryData g_Telemetry; +static HTELEMETRY g_tmContext; +static TmU8 *g_pTmMemoryArena = NULL; +static bool g_TelemetryLoaded = false; + +static unsigned int g_TelemetryFrameCount = 0; +static bool g_fTelemetryLevelChanged = false; + +static const TmU32 TELEMETRY_ARENA_SIZE = 8 * 1024 * 1024; // How much memory we want Telemetry to use. + +struct ThreadNameInfo_t +{ + TSLNodeBase_t base; + ThreadId_t ThreadID; + char szName[ 64 ]; +}; +static CTSSimpleList< ThreadNameInfo_t > g_ThreadNamesList; + +static bool g_bThreadNameArrayChanged = false; +static int g_ThreadNameArrayCount = 0; +static ThreadNameInfo_t *g_ThreadNameArray[64]; + +void TelemetryThreadSetDebugName( ThreadId_t id, const char *pszName ) +{ + ThreadNameInfo_t *pThreadNameInfo = new ThreadNameInfo_t; + + if( id == ( uint32 )-1 ) + { + id = ThreadGetCurrentId(); + } + + pThreadNameInfo->ThreadID = id; + strncpy( pThreadNameInfo->szName, pszName, ARRAYSIZE( pThreadNameInfo->szName ) ); + pThreadNameInfo->szName[ ARRAYSIZE( pThreadNameInfo->szName ) - 1 ] = 0; + g_ThreadNamesList.Push( pThreadNameInfo ); + + g_bThreadNameArrayChanged = true; +} + +static void UpdateTelemetryThreadNames() +{ + if( g_bThreadNameArrayChanged ) + { + // Go through and add any new thread names we got in our thread safe list to our thread names array. + for( ThreadNameInfo_t *pThreadNameInfo = g_ThreadNamesList.Pop(); + pThreadNameInfo; + pThreadNameInfo = g_ThreadNamesList.Pop() ) + { + if( g_ThreadNameArrayCount < ARRAYSIZE( g_ThreadNameArray ) ) + { + g_ThreadNameArray[ g_ThreadNameArrayCount ] = pThreadNameInfo; + g_ThreadNameArrayCount++; + } + else + { + delete pThreadNameInfo; + } + } + + tmThreadName( g_tmContext, ThreadGetCurrentId(), "MainThrd" ); + + for( int i = 0; i < g_ThreadNameArrayCount; i++ ) + { + tmThreadName( g_tmContext, g_ThreadNameArray[i]->ThreadID, g_ThreadNameArray[i]->szName ); + } + + g_bThreadNameArrayChanged = false; + } +} + +static bool TelemetryInitialize() +{ + if( g_tmContext ) + { + TmConnectionStatus status = tmGetConnectionStatus( g_tmContext ); + + if( status == TMCS_CONNECTED || status == TMCS_CONNECTING ) + return true; + } + + TmErrorCode retVal; + + if( !g_TelemetryLoaded ) + { + // Pass in 0 if you want to use the release mode DLL or 1 if you want to + // use the checked DLL. The checked DLL is compiled with optimizations but + // does extra run time checks and reporting. + int nLoadTelemetry = tmLoadTelemetry( 0 ); + + retVal = tmStartup(); + if ( retVal != TM_OK ) + { + Warning( "TelemetryInit() failed: tmStartup() returned %d, tmLoadTelemetry() returned %d.\n", retVal, nLoadTelemetry ); + return false; + } + + if( !g_pTmMemoryArena ) + { + g_pTmMemoryArena = new TmU8[ TELEMETRY_ARENA_SIZE ]; + } + + retVal = tmInitializeContext( &g_tmContext, g_pTmMemoryArena, TELEMETRY_ARENA_SIZE ); + if ( retVal != TM_OK ) + { + delete [] g_pTmMemoryArena; + g_pTmMemoryArena = NULL; + + Warning( "TelemetryInit() failed: tmInitializeContext() returned %d.\n", retVal ); + return false; + } + + g_TelemetryLoaded = true; + } + + const char *pGameName = "tf2"; + +#if defined( IS_WINDOWS_PC ) + char baseExeFilename[512]; + if( GetModuleFileName ( GetModuleHandle( NULL ), baseExeFilename, sizeof( baseExeFilename ) ) ) + { + char *pExt = strrchr( baseExeFilename, '.' ); + + if( pExt ) + *pExt = 0; + + char *pSeparator = strrchr( baseExeFilename, '\\' ); + + pGameName = pSeparator ? ( pSeparator + 1 ) : baseExeFilename; + } + + // If you've got \\perforce\symbols on your _NT_SYMBOL_PATH, tmOpen() can take a massively long + // time in the symInitialize() routine. Since we don't really need that, kill it here. + putenv( "_NT_SYMBOL_PATH=" ); +#endif + + const char *pServerAddress = g_Telemetry.ServerAddress[0] ? g_Telemetry.ServerAddress : "localhost"; + TmConnectionType tmType = !V_tier0_stricmp( pServerAddress, "FILE" ) ? TMCT_FILE : TMCT_TCP; + + Msg( "TELEMETRY: Calling tmOpen( %s )...\n", pServerAddress ); + + char szBuildInfo[ 2048 ]; + _snprintf( szBuildInfo, ARRAYSIZE( szBuildInfo ), "%s: %s", __DATE__ __TIME__, Plat_GetCommandLineA() ); + szBuildInfo[ ARRAYSIZE( szBuildInfo ) - 1 ] = 0; + + TmU32 TmOpenFlags = TMOF_DEFAULT | TMOF_MINIMAL_CONTEXT_SWITCHES; + /* TmOpenFlags |= TMOF_DISABLE_CONTEXT_SWITCHES | TMOF_INIT_NETWORKING*/ + + retVal = tmOpen( g_tmContext, pGameName, szBuildInfo, pServerAddress, tmType, + TELEMETRY_DEFAULT_PORT, TmOpenFlags, 1000 ); + if ( retVal != TM_OK ) + { + Warning( "TelemetryInitialize() failed: tmOpen returned %d.\n", retVal ); + return false; + } + + Msg( "Telemetry initialized at level %u.\n", g_Telemetry.Level ); + + // Make sure we set all the thread names. + g_bThreadNameArrayChanged = true; + UpdateTelemetryThreadNames(); + + return true; +} + +static void TelemetryShutdown( bool InDtor = false ) +{ + if( g_tmContext ) + { + // Msg can't be called here as tier0 may have already been shut down... + if( !InDtor ) + { + Msg( "Shutting down telemetry.\n" ); + } + + TmConnectionStatus status = tmGetConnectionStatus( g_tmContext ); + if( status == TMCS_CONNECTED || status == TMCS_CONNECTING ) + tmClose( g_tmContext ); + + // Discontinue new usage of the context before shutting it down (multithreading). + memset( g_Telemetry.tmContext, 0, sizeof( g_Telemetry.tmContext ) ); + HTELEMETRY hShutdown = g_tmContext; + g_tmContext = NULL; + + tmShutdownContext( hShutdown ); + tmShutdown(); + g_TelemetryLoaded = false; + } +} + +// Helper class to initialize Telemetry. +class CTelemetryRegister +{ +public: + CTelemetryRegister() {} + ~CTelemetryRegister() { TelemetryShutdown( true ); } +} g_TelemetryRegister; + +PLATFORM_INTERFACE void TelemetrySetLevel( unsigned int Level ) +{ + if( Level != g_Telemetry.Level ) + { + DevMsg( "TelemetrySetLevel changed from 0x%x to 0x%x\n", g_Telemetry.Level, Level ); + + g_Telemetry.Level = Level; + g_TelemetryFrameCount = g_Telemetry.FrameCount; + g_fTelemetryLevelChanged = true; + } +} + +static void TelemetryPlots() +{ + if( g_Telemetry.playbacktick ) + { + tmPlotU32( TELEMETRY_LEVEL1, TMPT_INTEGER, 0, g_Telemetry.playbacktick, "game/PlaybackTick" ); + g_Telemetry.playbacktick = 0; + } + + for( int i = 0; i < g_VProfCurrentProfile.GetNumCounters(); i++ ) + { + if( g_VProfCurrentProfile.GetCounterGroup( i ) == COUNTER_GROUP_TELEMETRY ) + { + int val; + const char *name = g_VProfCurrentProfile.GetCounterNameAndValue( i, val ); + + tmPlotI32( TELEMETRY_LEVEL1, TMPT_INTEGER, 0, val, name ); + } + } + + g_VProfCurrentProfile.ResetCounters( COUNTER_GROUP_TELEMETRY ); +} + +PLATFORM_INTERFACE void TelemetryTick() +{ + static double s_d0 = Plat_FloatTime(); + static TmU64 s_t0 = tmFastTime(); + + if( !g_Telemetry.Level && g_Telemetry.DemoTickStart && ( (uint32)g_Telemetry.playbacktick > g_Telemetry.DemoTickStart ) ) + { + TelemetrySetLevel( 2 ); + g_Telemetry.DemoTickStart = 0; + } + if( g_Telemetry.Level && g_Telemetry.DemoTickEnd && ( (uint32)g_Telemetry.playbacktick > g_Telemetry.DemoTickEnd ) ) + { + TelemetrySetLevel( 0 ); + g_Telemetry.DemoTickEnd = ( uint32 )-1; + } + + // People can NIL out contexts in the TelemetryData structure to control + // the level and what sections to log. We always need to do ticks though, + // so use master context for this. + if( g_tmContext ) + { + // Update any new thread names. + UpdateTelemetryThreadNames(); + + if ( g_Telemetry.Level > 0 ) + TelemetryPlots(); + + // Do a Telemetry Tick. + tmTick( g_tmContext ); + + // Update flRDTSCToMilliSeconds. + TmU64 s_t1 = tmFastTime(); + double s_d1 = Plat_FloatTime(); + + g_Telemetry.flRDTSCToMilliSeconds = 1000.0f / ( ( s_t1 - s_t0 ) / ( s_d1 - s_d0 ) ); + + s_d0 = s_d1; + s_t0 = s_t1; + + // Check if we're only supposed to run X amount of frames. + if( g_TelemetryFrameCount && !tmIsPaused( g_tmContext ) ) + { + g_TelemetryFrameCount--; + if( !g_TelemetryFrameCount ) + TelemetrySetLevel( 0 ); + } + } + + if( g_fTelemetryLevelChanged ) + { + g_fTelemetryLevelChanged = false; + memset( g_Telemetry.tmContext, 0, sizeof( g_Telemetry.tmContext ) ); + + if( g_Telemetry.Level == 0 ) + { + // Calling shutdown here invalidates all the telemetry context handles. + // Background threads in the middle of Tm__Zone'd calls may crash... + TelemetryShutdown(); + } + else + { + if( !TelemetryInitialize() ) + { + g_Telemetry.Level = 0; + } + else + { + tmPause( g_tmContext, 0 ); + + uint32 Level = MIN( g_Telemetry.Level, ARRAYSIZE( g_Telemetry.tmContext ) ); + for( uint32 i = 0; i < Level; i++ ) + { + g_Telemetry.tmContext[i] = g_tmContext; + } + } + } + + // TM_SET_TIMELINE_SECTION_NAME( g_tmContext, "Level:0x%x", g_Telemetry.Level ); + + // To disable various telemetry features, use the tmEnable() function as so: + // TM_ENABLE( g_tmContext, TMO_SUPPORT_PLOT, 0 ); + } +} + +#endif // RAD_TELEMETRY_ENABLED diff --git a/tier0/win32consoleio.cpp b/tier0/win32consoleio.cpp new file mode 100644 index 0000000..b83bc13 --- /dev/null +++ b/tier0/win32consoleio.cpp @@ -0,0 +1,91 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Attaches a console for I/O to a Win32 GUI application in a +// reasonably smart fashion +// +//============================================================================= + +#include "pch_tier0.h" + +#if defined( _WIN32 ) + +#include +#include +#include + +#include + +#endif // defined( _WIN32 ) + +//----------------------------------------------------------------------------- +// +// Attach a console to a Win32 GUI process and setup stdin, stdout & stderr +// along with the std::iostream (cout, cin, cerr) equivalents to read and +// write to and from that console +// +// 1. Ensure the handle associated with stdio is FILE_TYPE_UNKNOWN +// if it's anything else just return false. This supports cygwin +// style command shells like rxvt which setup pipes to processes +// they spawn +// +// 2. See if the Win32 function call AttachConsole exists in kernel32 +// It's a Windows 2000 and above call. If it does, call it and see +// if it succeeds in attaching to the console of the parent process. +// If that succeeds, return false (for no new console allocated). +// This supports someone typing the command from a normal windows +// command window and having the output go to the parent window. +// It's a little funny because a GUI app detaches so the command +// prompt gets intermingled with output from this process +// +// 3. If thigns get to here call AllocConsole which will pop open +// a new window and allow output to go to that window. The +// window will disappear when the process exists so if it's used +// for something like a help message then do something like getchar() +// from stdin to wait for a keypress. if AllocConsole is called +// true is returned. +// +// Return: true if AllocConsole() was used to pop open a new windows console +// +//----------------------------------------------------------------------------- + + +bool SetupWin32ConsoleIO() +{ +#if defined( _WIN32 ) + // Only useful on Windows platforms + + bool newConsole( false ); + + if ( GetFileType( GetStdHandle( STD_OUTPUT_HANDLE ) ) == FILE_TYPE_UNKNOWN ) + { + + HINSTANCE hInst = ::LoadLibrary( "kernel32.dll" ); + typedef BOOL ( WINAPI * pAttachConsole_t )( DWORD ); + pAttachConsole_t pAttachConsole( ( BOOL ( _stdcall * )( DWORD ) )GetProcAddress( hInst, "AttachConsole" ) ); + + if ( !( pAttachConsole && (*pAttachConsole)( ( DWORD ) - 1 ) ) ) + { + newConsole = true; + AllocConsole(); + } + + *stdout = *_fdopen( _open_osfhandle( reinterpret_cast< intptr_t >( GetStdHandle( STD_OUTPUT_HANDLE ) ), _O_TEXT ), "w" ); + setvbuf( stdout, NULL, _IONBF, 0 ); + + *stdin = *_fdopen( _open_osfhandle( reinterpret_cast< intptr_t >( GetStdHandle( STD_INPUT_HANDLE ) ), _O_TEXT ), "r" ); + setvbuf( stdin, NULL, _IONBF, 0 ); + + *stderr = *_fdopen( _open_osfhandle( reinterpret_cast< intptr_t >( GetStdHandle( STD_ERROR_HANDLE ) ), _O_TEXT ), "w" ); + setvbuf( stdout, NULL, _IONBF, 0 ); + + std::ios_base::sync_with_stdio(); + } + + return newConsole; + +#else // defined( _WIN32 ) + + return false; + +#endif // defined( _WIN32 ) +} \ No newline at end of file diff --git a/tier0/xbox/xbox_console.cpp b/tier0/xbox/xbox_console.cpp new file mode 100644 index 0000000..7e9532c --- /dev/null +++ b/tier0/xbox/xbox_console.cpp @@ -0,0 +1,31 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Xbox console link +// +//=====================================================================================// + +#include "pch_tier0.h" +#include "xbox/xbox_console.h" +#include "tier0/memdbgon.h" + +IXboxConsole *g_pXboxConsole; + +typedef IXboxConsole * (WINAPI *CONSOLEINTERFACEFUNC)( void ); + +void XboxConsoleInit() +{ + g_pXboxConsole = NULL; + + HMODULE hDLL = ::LoadLibrary( "vxbdm_360.dll" ); + if ( !hDLL ) + { + return; + } + + CONSOLEINTERFACEFUNC fpnGetConsoleInterface = (CONSOLEINTERFACEFUNC) ::GetProcAddress( hDLL, "GetConsoleInterface" ); + + if ( fpnGetConsoleInterface ) + { + g_pXboxConsole = fpnGetConsoleInterface(); + } +} diff --git a/tier0/xbox/xbox_profile.cpp b/tier0/xbox/xbox_profile.cpp new file mode 100644 index 0000000..db0ac3a --- /dev/null +++ b/tier0/xbox/xbox_profile.cpp @@ -0,0 +1,9 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Xbox profiling +// +//=====================================================================================// + +#include "pch_tier0.h" +#include "tier0/memdbgon.h" + diff --git a/tier0/xbox/xbox_system.cpp b/tier0/xbox/xbox_system.cpp new file mode 100644 index 0000000..2edfb47 --- /dev/null +++ b/tier0/xbox/xbox_system.cpp @@ -0,0 +1,368 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Xbox +// +//=====================================================================================// + +#include "pch_tier0.h" +#include "xbox/xbox_console.h" +#include "xbox/xbox_win32stubs.h" +#include "xbox/xbox_launch.h" +#include "tier0/memdbgon.h" + +#define XBX_MAX_EVENTS 32 + +xevent_t g_xbx_eventQueue[XBX_MAX_EVENTS]; +int g_xbx_eventHead; +int g_xbx_eventTail; +DWORD g_iStorageDeviceId = XBX_INVALID_STORAGE_ID; +DWORD g_iPrimaryUserId = XBX_INVALID_USER_ID; +DWORD g_InvitedUserId = XBX_INVALID_USER_ID; +HANDLE g_hListenHandle = INVALID_HANDLE_VALUE; +ULONG64 g_ListenCategories = 0; +XNKID g_InviteSessionId = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +//----------------------------------------------------------------------------- +// Convert an Xbox notification to a custom windows message +//----------------------------------------------------------------------------- +static int NotificationToWindowsMessage( DWORD id ) +{ + switch( id ) + { + case XN_SYS_UI: return WM_SYS_UI; + case XN_SYS_SIGNINCHANGED: return WM_SYS_SIGNINCHANGED; + case XN_SYS_STORAGEDEVICESCHANGED: return WM_SYS_STORAGEDEVICESCHANGED; + case XN_SYS_PROFILESETTINGCHANGED: return WM_SYS_PROFILESETTINGCHANGED; + case XN_SYS_MUTELISTCHANGED: return WM_SYS_MUTELISTCHANGED; + case XN_SYS_INPUTDEVICESCHANGED: return WM_SYS_INPUTDEVICESCHANGED; + case XN_SYS_INPUTDEVICECONFIGCHANGED: return WM_SYS_INPUTDEVICECONFIGCHANGED; + case XN_LIVE_CONNECTIONCHANGED: return WM_LIVE_CONNECTIONCHANGED; + case XN_LIVE_INVITE_ACCEPTED: return WM_LIVE_INVITE_ACCEPTED; + case XN_LIVE_LINK_STATE_CHANGED: return WM_LIVE_LINK_STATE_CHANGED; + case XN_LIVE_CONTENT_INSTALLED: return WM_LIVE_CONTENT_INSTALLED; + case XN_LIVE_MEMBERSHIP_PURCHASED: return WM_LIVE_MEMBERSHIP_PURCHASED; + case XN_LIVE_VOICECHAT_AWAY: return WM_LIVE_VOICECHAT_AWAY; + case XN_LIVE_PRESENCE_CHANGED: return WM_LIVE_PRESENCE_CHANGED; + case XN_FRIENDS_PRESENCE_CHANGED: return WM_FRIENDS_PRESENCE_CHANGED; + case XN_FRIENDS_FRIEND_ADDED: return WM_FRIENDS_FRIEND_ADDED; + case XN_FRIENDS_FRIEND_REMOVED: return WM_FRIENDS_FRIEND_REMOVED; + //deprecated in Jun08 XDK: case XN_CUSTOM_GAMEBANNERPRESSED: return WM_CUSTOM_GAMEBANNERPRESSED; + case XN_CUSTOM_ACTIONPRESSED: return WM_CUSTOM_ACTIONPRESSED; + case XN_XMP_STATECHANGED: return WM_XMP_STATECHANGED; + case XN_XMP_PLAYBACKBEHAVIORCHANGED: return WM_XMP_PLAYBACKBEHAVIORCHANGED; + case XN_XMP_PLAYBACKCONTROLLERCHANGED: return WM_XMP_PLAYBACKCONTROLLERCHANGED; + default: + Warning( "Unrecognized notification id %d\n", id ); + return 0; + } +} + +//----------------------------------------------------------------------------- +// XBX_Error +// +//----------------------------------------------------------------------------- +void XBX_Error( const char* format, ... ) +{ + va_list args; + char message[XBX_MAX_MESSAGE]; + + va_start( args, format ); + _vsnprintf( message, sizeof( message ), format, args ); + va_end( args ); + + message[sizeof( message )-1] = '\0'; + + XBX_DebugString( XMAKECOLOR(255,0,0), message ); + XBX_FlushDebugOutput(); + + DebugBreak(); + + static volatile int doReturn; + + while ( !doReturn ); +} + +//----------------------------------------------------------------------------- +// XBX_OutputDebugStringA +// +// Replaces 'OutputDebugString' to send through debugging channel +//----------------------------------------------------------------------------- +void XBX_OutputDebugStringA( LPCSTR lpOutputString ) +{ + XBX_DebugString( XMAKECOLOR(0,0,0), lpOutputString ); +} + +//----------------------------------------------------------------------------- +// XBX_GetEvent +// +//----------------------------------------------------------------------------- +static xevent_t* XBX_GetEvent(void) +{ + xevent_t* evPtr; + + if ( g_xbx_eventHead == g_xbx_eventTail ) + { + // empty + return NULL; + } + + evPtr = &g_xbx_eventQueue[g_xbx_eventHead & (XBX_MAX_EVENTS-1)]; + + // advance to next event + g_xbx_eventHead++; + + return evPtr; +} + +//----------------------------------------------------------------------------- +// XBX_FlushEvents +// +//----------------------------------------------------------------------------- +static void XBX_FlushEvents(void) +{ + g_xbx_eventHead = 0; + g_xbx_eventTail = 0; +} + +//----------------------------------------------------------------------------- +// XBX_ProcessXCommand +// +//----------------------------------------------------------------------------- +static void XBX_ProcessXCommand( const char *pCommand ) +{ + // remote command + // pass it game via windows message + HWND hWnd = GetFocus(); + WNDPROC windowProc = ( WNDPROC)GetWindowLong( hWnd, GWL_WNDPROC ); + if ( windowProc ) + { + windowProc( hWnd, WM_XREMOTECOMMAND, 0, (LPARAM)pCommand ); + } +} + +//----------------------------------------------------------------------------- +// XBX_ProcessListenerNotification +// +//----------------------------------------------------------------------------- +static void XBX_ProcessListenerNotification( int notification, int parameter ) +{ + // pass it game via windows message + HWND hWnd = GetFocus(); + WNDPROC windowProc = ( WNDPROC)GetWindowLong( hWnd, GWL_WNDPROC ); + if ( windowProc ) + { + windowProc( hWnd, notification, 0, (LPARAM)parameter ); + } +} + +//----------------------------------------------------------------------------- +// XBX_QueueEvent +// +//----------------------------------------------------------------------------- +void XBX_QueueEvent(xevent_e event, int arg1, int arg2, int arg3) +{ + xevent_t* evPtr; + + evPtr = &g_xbx_eventQueue[g_xbx_eventTail & (XBX_MAX_EVENTS-1)]; + evPtr->event = event; + evPtr->arg1 = arg1; + evPtr->arg2 = arg2; + evPtr->arg3 = arg3; + + // next slot, queue never fills just overwrite older events + g_xbx_eventTail++; +} + +//----------------------------------------------------------------------------- +// XBX_ProcessEvents +// +// Assumed one per frame only! +//----------------------------------------------------------------------------- +void XBX_ProcessEvents(void) +{ + xevent_t *evPtr; + + DWORD id; + ULONG parameter; + while ( XNotifyGetNext( g_hListenHandle, 0, &id, ¶meter ) ) + { + // Special handling + switch( id ) + { + case XN_SYS_STORAGEDEVICESCHANGED: + { + // Have we selected a storage device? + DWORD storageID = XBX_GetStorageDeviceId(); + if ( storageID == XBX_INVALID_STORAGE_ID || storageID == XBX_STORAGE_DECLINED ) + break; + + // Validate the selected storage device + XDEVICE_DATA deviceData; + DWORD ret = XContentGetDeviceData( storageID, &deviceData ); + if ( ret != ERROR_SUCCESS ) + { + // Device was removed + XBX_SetStorageDeviceId( XBX_INVALID_STORAGE_ID ); + XBX_QueueEvent( XEV_LISTENER_NOTIFICATION, NotificationToWindowsMessage( id ), 0, 0 ); + } + break; + } + + default: + XBX_QueueEvent( XEV_LISTENER_NOTIFICATION, NotificationToWindowsMessage( id ), parameter, 0 ); + break; + } + } + + // pump event queue + while ( 1 ) + { + evPtr = XBX_GetEvent(); + if ( !evPtr ) + break; + + switch ( evPtr->event ) + { + case XEV_REMOTECMD: + XBX_ProcessXCommand( (char *)evPtr->arg1 ); + // clear to mark as absorbed + ((char *)evPtr->arg1)[0] = '\0'; + break; + + case XEV_LISTENER_NOTIFICATION: + XBX_ProcessListenerNotification( evPtr->arg1, evPtr->arg2 ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// XBX_NotifyCreateListener +// +// Add notification categories to the listener object +//----------------------------------------------------------------------------- +bool XBX_NotifyCreateListener( ULONG64 categories ) +{ + if ( categories != 0 ) + { + categories |= g_ListenCategories; + } + + g_hListenHandle = XNotifyCreateListener( categories ); + if ( g_hListenHandle == NULL || g_hListenHandle == INVALID_HANDLE_VALUE ) + { + return false; + } + + g_ListenCategories = categories; + return true; +} + +//----------------------------------------------------------------------------- +// XBX_GetLanguageString +// +// Returns the xbox language setting as a string +//----------------------------------------------------------------------------- +const char* XBX_GetLanguageString( void ) +{ + switch( XGetLanguage() ) + { + case XC_LANGUAGE_FRENCH: + return "french"; + case XC_LANGUAGE_GERMAN: + return "german"; + } + + return "english"; +} + +bool XBX_IsLocalized( void ) +{ + switch( XGetLanguage() ) + { + case XC_LANGUAGE_FRENCH: + case XC_LANGUAGE_GERMAN: + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// XBX_GetStorageDeviceId +// +// Returns the xbox storage device ID +//----------------------------------------------------------------------------- +DWORD XBX_GetStorageDeviceId( void ) +{ + return g_iStorageDeviceId; +} + +//----------------------------------------------------------------------------- +// XBX_SetStorageDeviceId +// +// Sets the xbox storage device ID +//----------------------------------------------------------------------------- +void XBX_SetStorageDeviceId( DWORD id ) +{ + g_iStorageDeviceId = id; +} + +//----------------------------------------------------------------------------- +// XBX_GetPrimaryUserId +// +// Returns the active user ID +//----------------------------------------------------------------------------- +DWORD XBX_GetPrimaryUserId( void ) +{ + return g_iPrimaryUserId; +} + +//----------------------------------------------------------------------------- +// XBX_SetPrimaryUserId +// +// Sets the active user ID +//----------------------------------------------------------------------------- +void XBX_SetPrimaryUserId( DWORD idx ) +{ + g_iPrimaryUserId = idx; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the stored session ID for a cross-game invite +//----------------------------------------------------------------------------- +XNKID XBX_GetInviteSessionId( void ) +{ + return g_InviteSessionId; +} + +//----------------------------------------------------------------------------- +// Purpose: Store a session ID for an invitation +//----------------------------------------------------------------------------- +void XBX_SetInviteSessionId( XNKID nSessionId ) +{ + g_InviteSessionId = nSessionId; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the Id of the user who received an invite +//----------------------------------------------------------------------------- + +DWORD XBX_GetInvitedUserId( void ) +{ + return g_InvitedUserId; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the Id of the user who received an invite +//----------------------------------------------------------------------------- +void XBX_SetInvitedUserId( DWORD nUserId ) +{ + g_InvitedUserId = nUserId; +} + +static CXboxLaunch g_XBoxLaunch; +CXboxLaunch *XboxLaunch() +{ + return &g_XBoxLaunch; +} diff --git a/tier0/xbox/xbox_win32stubs.cpp b/tier0/xbox/xbox_win32stubs.cpp new file mode 100644 index 0000000..95d0b53 --- /dev/null +++ b/tier0/xbox/xbox_win32stubs.cpp @@ -0,0 +1,617 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: XBox win32 replacements - Mocks trivial windows flow +// +//=============================================================================// + +#include "pch_tier0.h" +#include "xbox/xbox_win32stubs.h" +#include "tier0/memdbgon.h" + +// On the 360, threads can run on any of 6 logical processors +DWORD g_dwProcessAffinityMask = 0x3F; + +#define HWND_MAGIC 0x12345678 + +struct xWndClass_t +{ + char* pClassName; + WNDPROC wndProc; + xWndClass_t* pNext; +}; + +struct xWnd_t +{ + xWndClass_t* pWndClass; + int x; + int y; + int w; + int h; + long windowLongs[GWL_MAX]; + int show; + int nMagic; + xWnd_t* pNext; +}; + +static xWndClass_t* g_pWndClasses; +static xWnd_t* g_pWnds; +static HWND g_focusWindow; + +inline bool IsWndValid( HWND hWnd ) +{ + if ( !hWnd || ((xWnd_t*)hWnd)->nMagic != HWND_MAGIC ) + return false; + return true; +} + +int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) +{ + XBX_Error( lpText ); + Assert( 0 ); + + return (0); +} + +LONG GetWindowLong(HWND hWnd, int nIndex) +{ + LONG oldLong; + + if ( !IsWndValid( hWnd ) ) + return 0; + + switch (nIndex) + { + case GWL_WNDPROC: + case GWL_USERDATA: + case GWL_STYLE: + case GWL_EXSTYLE: + oldLong = ((xWnd_t*)hWnd)->windowLongs[nIndex]; + break; + default: + // not implemented + Assert( 0 ); + return 0; + } + + return oldLong; +} + +LONG_PTR GetWindowLongPtr(HWND hWnd, int nIndex) +{ + UINT idx; + + switch ( nIndex ) + { + case GWLP_WNDPROC: + idx = GWL_WNDPROC; + break; + case GWLP_USERDATA: + idx = GWL_USERDATA; + break; + default: + // not implemented + Assert(0); + return 0; + } + + return GetWindowLong( hWnd, idx ); +} + +LONG_PTR GetWindowLongPtrW(HWND hWnd, int nIndex) +{ + AssertMsg( false, "GetWindowLongPtrW does not exist on Xbox 360." ); + return GetWindowLongPtr( hWnd, nIndex ); +} + + +LONG SetWindowLong(HWND hWnd, int nIndex, LONG dwNewLong) +{ + LONG oldLong; + + if ( !IsWndValid( hWnd ) ) + return 0; + + switch ( nIndex ) + { + case GWL_WNDPROC: + case GWL_USERDATA: + case GWL_STYLE: + oldLong = ((xWnd_t*)hWnd)->windowLongs[nIndex]; + ((xWnd_t*)hWnd)->windowLongs[nIndex] = dwNewLong; + break; + default: + // not implemented + Assert( 0 ); + return 0; + } + + return oldLong; +} + +LONG_PTR SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong) +{ + UINT idx; + + switch ( nIndex ) + { + case GWLP_WNDPROC: + idx = GWL_WNDPROC; + break; + case GWLP_USERDATA: + idx = GWL_USERDATA; + break; + default: + // not implemented + Assert( 0 ); + return 0; + } + + return SetWindowLong( hWnd, idx, dwNewLong ); +} + +LONG_PTR SetWindowLongPtrW(HWND hWnd, int nIndex, LONG_PTR dwNewLong) +{ + AssertMsg( false, "SetWindowLongPtrW does not exist on Xbox 360." ); + return SetWindowLongPtr( hWnd, nIndex, dwNewLong ); +} + +HWND CreateWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) +{ + // find classname + xWndClass_t* pWndClass = g_pWndClasses; + while ( pWndClass ) + { + if ( !stricmp( lpClassName, pWndClass->pClassName ) ) + break; + pWndClass = pWndClass->pNext; + } + if ( !pWndClass ) + { + // no such class + return (HWND)NULL; + } + + // allocate and setup + xWnd_t* pWnd = new xWnd_t; + memset( pWnd, 0, sizeof(xWnd_t) ); + pWnd->pWndClass = pWndClass; + pWnd->windowLongs[GWL_WNDPROC] = (LONG)pWndClass->wndProc; + pWnd->windowLongs[GWL_STYLE] = dwStyle; + pWnd->x = x; + pWnd->y = y; + pWnd->w = nWidth; + pWnd->h = nHeight; + pWnd->nMagic = HWND_MAGIC; + + // link into list + pWnd->pNext = g_pWnds; + g_pWnds = pWnd; + + // force the focus + g_focusWindow = (HWND)pWnd; + + // send the expected message sequence + SendMessage( (HWND)pWnd, WM_CREATE, 0, 0 ); + SendMessage( (HWND)pWnd, WM_ACTIVATEAPP, TRUE, 0 ); + + return (HWND)pWnd; +} + +HWND CreateWindowEx(DWORD dwExStyle, LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam) +{ + return CreateWindow( lpClassName, lpWindowName, dwStyle, x, y, nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam ); +} + +BOOL DestroyWindow( HWND hWnd ) +{ + if ( !IsWndValid( hWnd ) ) + return FALSE; + + xWnd_t* pPrev = g_pWnds; + xWnd_t* pCur = g_pWnds; + + while ( pCur ) + { + if ( pCur == (xWnd_t*)hWnd ) + { + if ( pPrev == g_pWnds ) + { + // at head of list, fixup + g_pWnds = pCur->pNext; + } + else + { + // remove from chain + pPrev->pNext = pCur->pNext; + } + pCur->nMagic = 0; + delete pCur; + break; + } + + // advance through list + pPrev = pCur; + pCur = pCur->pNext; + } + + return TRUE; +} + +ATOM RegisterClassEx(CONST WNDCLASSEX *lpwcx) +{ + // create + xWndClass_t* pWndClass = new xWndClass_t; + memset(pWndClass, 0, sizeof(xWndClass_t)); + pWndClass->pClassName = new char[strlen(lpwcx->lpszClassName)+1]; + strcpy(pWndClass->pClassName, lpwcx->lpszClassName); + pWndClass->wndProc = lpwcx->lpfnWndProc; + + // insert into list + pWndClass->pNext = g_pWndClasses; + g_pWndClasses = pWndClass; + + return (ATOM)pWndClass; +} + +ATOM RegisterClass(CONST WNDCLASS *lpwc) +{ + // create + xWndClass_t* pWndClass = new xWndClass_t; + memset(pWndClass, 0, sizeof(xWndClass_t)); + pWndClass->pClassName = new char[strlen(lpwc->lpszClassName)+1]; + strcpy(pWndClass->pClassName, lpwc->lpszClassName); + pWndClass->wndProc = lpwc->lpfnWndProc; + + // insert into list + pWndClass->pNext = g_pWndClasses; + g_pWndClasses = pWndClass; + + return (ATOM)pWndClass; +} + +HWND GetFocus(VOID) +{ + if ( !IsWndValid( g_focusWindow ) ) + return NULL; + + return g_focusWindow; +} + +HWND SetFocus( HWND hWnd ) +{ + HWND hOldFocus = g_focusWindow; + + if ( IsWndValid( hWnd ) ) + { + g_focusWindow = hWnd; + } + + return hOldFocus; +} + + +LRESULT CallWindowProc(WNDPROC lpPrevWndFunc, HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + return (lpPrevWndFunc(hWnd, Msg, wParam, lParam)); +} + +int GetSystemMetrics(int nIndex) +{ + XVIDEO_MODE videoMode; + XGetVideoMode( &videoMode ); + // default to having the backbuffer the same as the mode resolution. + int nFrameBufferWidth, nFrameBufferHeight; + nFrameBufferWidth = videoMode.dwDisplayWidth; + nFrameBufferHeight = videoMode.dwDisplayHeight; + + // override for cases where we need to have a different backbuffer either for memory reasons + // or for dealing with anamorphic modes. + if ( !videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 640 && videoMode.dwDisplayHeight == 576 ) + { + // PAL normal + nFrameBufferWidth = 640; + nFrameBufferHeight = 480; + } + else if ( videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 640 && videoMode.dwDisplayHeight == 576 ) + { + // PAL widescreen + nFrameBufferWidth = 848; + nFrameBufferHeight = 480; + } + else if ( videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 640 && videoMode.dwDisplayHeight == 480 ) + { + // anamorphic + nFrameBufferWidth = 848; + nFrameBufferHeight = 480; + } + else if ( videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 1024 && videoMode.dwDisplayHeight == 768 ) + { + // anamorphic + nFrameBufferWidth = 1280; + nFrameBufferHeight = 720; + } + else if ( videoMode.dwDisplayWidth == 1280 && videoMode.dwDisplayHeight == 760 ) + { + nFrameBufferWidth = 1280; + nFrameBufferHeight = 720; + } + else if ( videoMode.dwDisplayWidth == 1280 && videoMode.dwDisplayHeight == 768 ) + { + nFrameBufferWidth = 1280; + nFrameBufferHeight = 720; + } + else if ( !videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 1280 && videoMode.dwDisplayHeight == 1024 ) + { + nFrameBufferWidth = 1024; + nFrameBufferHeight = 768; + } + else if ( videoMode.fIsWideScreen && videoMode.dwDisplayWidth == 1280 && videoMode.dwDisplayHeight == 1024 ) + { + // anamorphic + nFrameBufferWidth = 1280; + nFrameBufferHeight = 720; + } + else if ( videoMode.dwDisplayWidth == 1360 && videoMode.dwDisplayHeight == 768 ) + { + nFrameBufferWidth = 1280; + nFrameBufferHeight = 720; + } + else if ( videoMode.dwDisplayWidth == 1920 && videoMode.dwDisplayHeight == 1080 ) + { + nFrameBufferWidth = 1280; + nFrameBufferHeight = 720; + } + + switch ( nIndex ) + { + case SM_CXFIXEDFRAME: + case SM_CYFIXEDFRAME: + case SM_CYSIZE: + return 0; + case SM_CXSCREEN: + return nFrameBufferWidth; + case SM_CYSCREEN: + return nFrameBufferHeight; + } + + // not implemented + Assert( 0 ); + return 0; +} + +BOOL ShowWindow(HWND hWnd, int nCmdShow) +{ + if ( !IsWndValid( hWnd ) ) + return FALSE; + + ((xWnd_t*)hWnd)->show = nCmdShow; + + if ((nCmdShow == SW_SHOWDEFAULT) || (nCmdShow == SW_SHOWNORMAL) || (nCmdShow == SW_SHOW)) + g_focusWindow = hWnd; + + return TRUE; +} + +LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + if ( !IsWndValid( hWnd ) ) + return 0L; + + xWnd_t* pWnd = (xWnd_t*)hWnd; + WNDPROC wndProc = (WNDPROC)pWnd->windowLongs[GWL_WNDPROC]; + Assert( wndProc ); + LRESULT result = wndProc(hWnd, Msg, wParam, lParam); + + return result; +} + +LRESULT SendMessageTimeout( HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam, UINT fuFlags, UINT uTimeout, PDWORD_PTR lpdwResult ) +{ + *lpdwResult = SendMessage( hWnd, Msg, wParam, lParam ); + + return -1; +} + +BOOL GetClientRect(HWND hWnd, LPRECT lpRect) +{ + if ( !IsWndValid( hWnd ) ) + return FALSE; + + xWnd_t* pWnd = (xWnd_t*)hWnd; + lpRect->left = 0; + lpRect->top = 0; + lpRect->right = pWnd->w; + lpRect->bottom = pWnd->h; + + return TRUE; +} + +int GetDeviceCaps(HDC hdc, int nIndex) +{ + switch (nIndex) + { + case HORZRES: + return GetSystemMetrics( SM_CXSCREEN ); + case VERTRES: + return GetSystemMetrics( SM_CYSCREEN ); + case VREFRESH: + return 60; + } + + Assert( 0 ); + return 0; +} + +BOOL SetWindowPos( HWND hWnd, HWND hWndInsertAfter, int x, int y, int cx, int cy, UINT uFlags ) +{ + if ( !IsWndValid( hWnd ) ) + return FALSE; + + xWnd_t* pWnd = (xWnd_t*)hWnd; + + if ( !( uFlags & SWP_NOMOVE ) ) + { + pWnd->x = x; + pWnd->y = y; + } + + if ( !( uFlags & SWP_NOSIZE ) ) + { + pWnd->w = cx; + pWnd->h = cy; + } + + return TRUE; +} + +int XBX_unlink( const char* filename ) +{ + bool bSuccess = DeleteFile( filename ) != 0; + if ( !bSuccess ) + { + if ( GetLastError() == ERROR_FILE_NOT_FOUND ) + { + // not a real failure + return 0; + } + } + // 0 = sucess, -1 = failure + return bSuccess ? 0 : -1; +} + +int XBX_mkdir( const char *pszDir ) +{ + char dirPath[MAX_PATH]; + char* ptr; + BOOL bSuccess; + + // prime and skip to first seperator after the drive path + // must create directory one path at a time + bSuccess = false; + strcpy( dirPath, pszDir ); + ptr = strchr( dirPath, '\\' ); + while ( ptr ) + { + ptr = strchr( ptr+1, '\\' ); + if ( ptr ) + { + *ptr = '\0'; + bSuccess = CreateDirectory( dirPath, XBOX_DONTCARE ); + if ( !bSuccess && GetLastError() == ERROR_ALREADY_EXISTS ) + { + // not a real error + bSuccess = true; + } + *ptr = '\\'; + } + } + + return ( bSuccess ? 0 : -1 ); +} + +char *XBX_getcwd( char *buf, size_t size ) +{ + if ( !buf ) + { + buf = (char*)malloc( 4 ); + } + strncpy( buf, "D:", size ); + return buf; +} + +int XBX_access( const char *path, int mode ) +{ + if ( !path ) + { + return -1; + } + + // get the fatx attributes + DWORD dwAttr = GetFileAttributes( path ); + if ( dwAttr == (DWORD)-1 ) + { + return -1; + } + + if ( mode == 0 ) + { + // is file exist? + return 0; + } + else if ( mode == 2 ) + { + // is file write only? + return -1; + } + else if ( mode == 4 ) + { + // is file read only? + if ( dwAttr & FILE_ATTRIBUTE_READONLY ) + return 0; + else + return -1; + } + else if ( mode == 6 ) + { + // is file read and write? + if ( !( dwAttr & FILE_ATTRIBUTE_READONLY ) ) + return 0; + else + return -1; + } + + return -1; +} + +DWORD XBX_GetCurrentDirectory( DWORD nBufferLength, LPTSTR lpBuffer ) +{ + XBX_getcwd( lpBuffer, nBufferLength ); + return strlen( lpBuffer ); +} + +DWORD XBX_GetModuleFileName( HMODULE hModule, LPTSTR lpFilename, DWORD nSize ) +{ + int len; + char *pStr; + char *pEnd; + char xexName[MAX_PATH]; + + if ( hModule == GetModuleHandle( NULL ) ) + { + // isolate xex of command line + pStr = GetCommandLine(); + if ( pStr ) + { + // cull possible quotes around xex + if ( pStr[0] == '\"' ) + { + pStr++; + pEnd = strchr( pStr, '\"' ); + if ( !pEnd ) + { + // no ending matching quote + return 0; + } + } + else + { + // find possible first argument + pEnd = strchr( lpFilename, ' ' ); + if ( !pEnd ) + { + pEnd = pStr+strlen( pStr ); + } + } + len = pEnd-pStr; + memcpy( xexName, pStr, len ); + xexName[len] = '\0'; + + len = _snprintf( lpFilename, nSize, "D:\\%s", xexName ); + if ( len == -1 ) + lpFilename[nSize-1] = '\0'; + + return strlen( lpFilename ); + } + } + return 0; +} diff --git a/tier1/strtools.cpp b/tier1/strtools.cpp index 2c5f72f..05f8bfa 100644 --- a/tier1/strtools.cpp +++ b/tier1/strtools.cpp @@ -1957,6 +1957,44 @@ const char * V_UnqualifiedFileName( const char * in ) return out; } +int V_StringToIntArray(int* pVector, int count, const char* pString) +{ + char* pstr, * pfront, tempString[128]; + int j; + + V_strncpy(tempString, pString, sizeof(tempString)); + pstr = pfront = tempString; + + for (j = 0; j < count; j++) // lifted from pr_edict.c + { + pVector[j] = atoi(pfront); + + while (*pstr && *pstr != ' ') + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + int nFound = j + 1; + + for (j++; j < count; j++) + { + pVector[j] = 0; + } + + return nFound; +} +void V_StringToColor32(color32* color, const char* pString) +{ + int tmp[4]; + int nCount = V_StringToIntArray(tmp, 4, pString); + color->r = tmp[0]; + color->g = tmp[1]; + color->b = tmp[2]; + color->a = (nCount == 4) ? tmp[3] : 255; +} //----------------------------------------------------------------------------- // Purpose: Composes a path and filename together, inserting a path separator diff --git a/tier2/beamsegdraw.cpp b/tier2/beamsegdraw.cpp new file mode 100644 index 0000000..fca4471 --- /dev/null +++ b/tier2/beamsegdraw.cpp @@ -0,0 +1,235 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "tier2/beamsegdraw.h" +#include "materialsystem/imaterialvar.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// +// CBeamSegDraw implementation. +// +//----------------------------------------------------------------------------- +void CBeamSegDraw::Start( IMatRenderContext *pRenderContext, int nSegs, IMaterial *pMaterial, CMeshBuilder *pMeshBuilder, int nMeshVertCount ) +{ + m_pRenderContext = pRenderContext; + Assert( nSegs >= 2 ); + + m_nSegsDrawn = 0; + m_nTotalSegs = nSegs; + + if ( pMeshBuilder ) + { + m_pMeshBuilder = pMeshBuilder; + m_nMeshVertCount = nMeshVertCount; + } + else + { + m_pMeshBuilder = NULL; + m_nMeshVertCount = 0; + + IMesh *pMesh = m_pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + m_Mesh.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, (nSegs-1) * 2 ); + } +} + +inline void CBeamSegDraw::ComputeNormal( const Vector &vecCameraPos, const Vector &vStartPos, const Vector &vNextPos, Vector *pNormal ) +{ + // vTangentY = line vector for beam + Vector vTangentY; + VectorSubtract( vStartPos, vNextPos, vTangentY ); + + // vDirToBeam = vector from viewer origin to beam + Vector vDirToBeam; + VectorSubtract( vStartPos, vecCameraPos, vDirToBeam ); + + // Get a vector that is perpendicular to us and perpendicular to the beam. + // This is used to fatten the beam. + CrossProduct( vTangentY, vDirToBeam, *pNormal ); + VectorNormalizeFast( *pNormal ); +} + +inline void CBeamSegDraw::SpecifySeg( const Vector &vecCameraPos, const Vector &vNormal ) +{ + // SUCKY: Need to do a fair amount more work to get the tangent owing to the averaged normal + Vector vDirToBeam, vTangentY; + VectorSubtract( m_Seg.m_vPos, vecCameraPos, vDirToBeam ); + CrossProduct( vDirToBeam, vNormal, vTangentY ); + VectorNormalizeFast( vTangentY ); + + // Build the endpoints. + Vector vPoint1, vPoint2; + VectorMA( m_Seg.m_vPos, m_Seg.m_flWidth*0.5f, vNormal, vPoint1 ); + VectorMA( m_Seg.m_vPos, -m_Seg.m_flWidth*0.5f, vNormal, vPoint2 ); + + if ( m_pMeshBuilder ) + { + // Specify the points. + m_pMeshBuilder->Position3fv( vPoint1.Base() ); + m_pMeshBuilder->Color4f( VectorExpand( m_Seg.m_vColor ), m_Seg.m_flAlpha ); + m_pMeshBuilder->TexCoord2f( 0, 0, m_Seg.m_flTexCoord ); + m_pMeshBuilder->TexCoord2f( 1, 0, m_Seg.m_flTexCoord ); + m_pMeshBuilder->TangentS3fv( vNormal.Base() ); + m_pMeshBuilder->TangentT3fv( vTangentY.Base() ); + m_pMeshBuilder->AdvanceVertex(); + + m_pMeshBuilder->Position3fv( vPoint2.Base() ); + m_pMeshBuilder->Color4f( VectorExpand( m_Seg.m_vColor ), m_Seg.m_flAlpha ); + m_pMeshBuilder->TexCoord2f( 0, 1, m_Seg.m_flTexCoord ); + m_pMeshBuilder->TexCoord2f( 1, 1, m_Seg.m_flTexCoord ); + m_pMeshBuilder->TangentS3fv( vNormal.Base() ); + m_pMeshBuilder->TangentT3fv( vTangentY.Base() ); + m_pMeshBuilder->AdvanceVertex(); + + if ( m_nSegsDrawn > 1 ) + { + int nBase = ( ( m_nSegsDrawn - 2 ) * 2 ) + m_nMeshVertCount; + + m_pMeshBuilder->FastIndex( nBase ); + m_pMeshBuilder->FastIndex( nBase + 1 ); + m_pMeshBuilder->FastIndex( nBase + 2 ); + m_pMeshBuilder->FastIndex( nBase + 1 ); + m_pMeshBuilder->FastIndex( nBase + 3 ); + m_pMeshBuilder->FastIndex( nBase + 2 ); + } + } + else + { + // Specify the points. + m_Mesh.Position3fv( vPoint1.Base() ); + m_Mesh.Color4f( VectorExpand( m_Seg.m_vColor ), m_Seg.m_flAlpha ); + m_Mesh.TexCoord2f( 0, 0, m_Seg.m_flTexCoord ); + m_Mesh.TexCoord2f( 1, 0, m_Seg.m_flTexCoord ); + m_Mesh.TangentS3fv( vNormal.Base() ); + m_Mesh.TangentT3fv( vTangentY.Base() ); + m_Mesh.AdvanceVertex(); + + m_Mesh.Position3fv( vPoint2.Base() ); + m_Mesh.Color4f( VectorExpand( m_Seg.m_vColor ), m_Seg.m_flAlpha ); + m_Mesh.TexCoord2f( 0, 1, m_Seg.m_flTexCoord ); + m_Mesh.TexCoord2f( 1, 1, m_Seg.m_flTexCoord ); + m_Mesh.TangentS3fv( vNormal.Base() ); + m_Mesh.TangentT3fv( vTangentY.Base() ); + m_Mesh.AdvanceVertex(); + } +} + +void CBeamSegDraw::NextSeg( BeamSeg_t *pSeg ) +{ + Vector vecCameraPos; + m_pRenderContext->GetWorldSpaceCameraPosition( &vecCameraPos ); + + if ( m_nSegsDrawn > 0 ) + { + // Get a vector that is perpendicular to us and perpendicular to the beam. + // This is used to fatten the beam. + Vector vNormal, vAveNormal; + ComputeNormal( vecCameraPos, m_Seg.m_vPos, pSeg->m_vPos, &vNormal ); + + if ( m_nSegsDrawn > 1 ) + { + // Average this with the previous normal + VectorAdd( vNormal, m_vNormalLast, vAveNormal ); + vAveNormal *= 0.5f; + VectorNormalizeFast( vAveNormal ); + } + else + { + vAveNormal = vNormal; + } + + m_vNormalLast = vNormal; + SpecifySeg( vecCameraPos, vAveNormal ); + } + + m_Seg = *pSeg; + ++m_nSegsDrawn; + + if( m_nSegsDrawn == m_nTotalSegs ) + { + SpecifySeg( vecCameraPos, m_vNormalLast ); + } +} + +void CBeamSegDraw::End() +{ + if ( m_pMeshBuilder ) + { + m_pMeshBuilder = NULL; + return; + } + + m_Mesh.End( false, true ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBeamSegDrawArbitrary::SetNormal( const Vector &normal ) +{ + m_vNormalLast = normal; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CBeamSegDrawArbitrary::NextSeg( BeamSeg_t *pSeg ) +{ + if ( m_nSegsDrawn > 0 ) + { + Vector segDir = ( m_PrevSeg.m_vPos - pSeg->m_vPos ); + VectorNormalize( segDir ); + + Vector normal = CrossProduct( segDir, m_vNormalLast ); + SpecifySeg( normal ); + } + + m_PrevSeg = m_Seg; + m_Seg = *pSeg; + ++m_nSegsDrawn; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : &vNextPos - +//----------------------------------------------------------------------------- +void CBeamSegDrawArbitrary::SpecifySeg( const Vector &vNormal ) +{ + // Build the endpoints. + Vector vPoint1, vPoint2; + Vector vDelta; + VectorMultiply( vNormal, m_Seg.m_flWidth*0.5f, vDelta ); + VectorAdd( m_Seg.m_vPos, vDelta, vPoint1 ); + VectorSubtract( m_Seg.m_vPos, vDelta, vPoint2 ); + + // Specify the points. + Assert( IsFinite(m_Seg.m_vColor.x) && IsFinite(m_Seg.m_vColor.y) && IsFinite(m_Seg.m_vColor.z) && IsFinite(m_Seg.m_flAlpha) ); + Assert( (m_Seg.m_vColor.x >= 0.0) && (m_Seg.m_vColor.y >= 0.0) && (m_Seg.m_vColor.z >= 0.0) && (m_Seg.m_flAlpha >= 0.0) ); + Assert( (m_Seg.m_vColor.x <= 1.0) && (m_Seg.m_vColor.y <= 1.0) && (m_Seg.m_vColor.z <= 1.0) && (m_Seg.m_flAlpha <= 1.0) ); + + unsigned char r = FastFToC( m_Seg.m_vColor.x ); + unsigned char g = FastFToC( m_Seg.m_vColor.y ); + unsigned char b = FastFToC( m_Seg.m_vColor.z ); + unsigned char a = FastFToC( m_Seg.m_flAlpha ); + m_Mesh.Position3fv( vPoint1.Base() ); + m_Mesh.Color4ub( r, g, b, a ); + m_Mesh.TexCoord2f( 0, 0, m_Seg.m_flTexCoord ); + m_Mesh.TexCoord2f( 1, 0, m_Seg.m_flTexCoord ); + m_Mesh.AdvanceVertex(); + + m_Mesh.Position3fv( vPoint2.Base() ); + m_Mesh.Color4ub( r, g, b, a ); + m_Mesh.TexCoord2f( 0, 1, m_Seg.m_flTexCoord ); + m_Mesh.TexCoord2f( 1, 1, m_Seg.m_flTexCoord ); + m_Mesh.AdvanceVertex(); +} diff --git a/tier2/camerautils.cpp b/tier2/camerautils.cpp new file mode 100644 index 0000000..41d1dc7 --- /dev/null +++ b/tier2/camerautils.cpp @@ -0,0 +1,99 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +#include "tier2/camerautils.h" +#include "tier0/dbg.h" +#include "mathlib/vector.h" +#include "mathlib/vmatrix.h" +#include "tier2/tier2.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// accessors for generated matrices +//----------------------------------------------------------------------------- +void ComputeViewMatrix( matrix3x4_t *pWorldToCamera, const Camera_t &camera ) +{ + matrix3x4_t transform; + AngleMatrix( camera.m_angles, camera.m_origin, transform ); + + VMatrix matRotate( transform ); + VMatrix matRotateZ; + MatrixBuildRotationAboutAxis( matRotateZ, Vector(0,0,1), -90 ); + MatrixMultiply( matRotate, matRotateZ, matRotate ); + + VMatrix matRotateX; + MatrixBuildRotationAboutAxis( matRotateX, Vector(1,0,0), 90 ); + MatrixMultiply( matRotate, matRotateX, matRotate ); + transform = matRotate.As3x4(); + + MatrixInvert( transform, *pWorldToCamera ); +} + +void ComputeViewMatrix( VMatrix *pWorldToCamera, const Camera_t &camera ) +{ + matrix3x4_t transform, invTransform; + AngleMatrix( camera.m_angles, camera.m_origin, transform ); + + VMatrix matRotate( transform ); + VMatrix matRotateZ; + MatrixBuildRotationAboutAxis( matRotateZ, Vector(0,0,1), -90 ); + MatrixMultiply( matRotate, matRotateZ, matRotate ); + + VMatrix matRotateX; + MatrixBuildRotationAboutAxis( matRotateX, Vector(1,0,0), 90 ); + MatrixMultiply( matRotate, matRotateX, matRotate ); + transform = matRotate.As3x4(); + + MatrixInvert( transform, invTransform ); + *pWorldToCamera = invTransform; +} + +void ComputeProjectionMatrix( VMatrix *pCameraToProjection, const Camera_t &camera, int width, int height ) +{ + float flFOV = camera.m_flFOV; + float flZNear = camera.m_flZNear; + float flZFar = camera.m_flZFar; + float flApsectRatio = (float)width / (float)height; + +// MatrixBuildPerspective( proj, flFOV, flFOV * flApsectRatio, flZNear, flZFar ); + +#if 1 + float halfWidth = tan( flFOV * M_PI / 360.0 ); + float halfHeight = halfWidth / flApsectRatio; +#else + float halfHeight = tan( flFOV * M_PI / 360.0 ); + float halfWidth = flApsectRatio * halfHeight; +#endif + memset( pCameraToProjection, 0, sizeof( VMatrix ) ); + pCameraToProjection->m[0][0] = 1.0f / halfWidth; + pCameraToProjection->m[1][1] = 1.0f / halfHeight; + pCameraToProjection->m[2][2] = flZFar / ( flZNear - flZFar ); + pCameraToProjection->m[3][2] = -1.0f; + pCameraToProjection->m[2][3] = flZNear * flZFar / ( flZNear - flZFar ); +} + + +//----------------------------------------------------------------------------- +// Computes the screen space position given a screen size +//----------------------------------------------------------------------------- +void ComputeScreenSpacePosition( Vector2D *pScreenPosition, const Vector &vecWorldPosition, + const Camera_t &camera, int width, int height ) +{ + VMatrix view, proj, viewproj; + ComputeViewMatrix( &view, camera ); + ComputeProjectionMatrix( &proj, camera, width, height ); + MatrixMultiply( proj, view, viewproj ); + + Vector vecScreenPos; + Vector3DMultiplyPositionProjective( viewproj, vecWorldPosition, vecScreenPos ); + + pScreenPosition->x = ( vecScreenPos.x + 1.0f ) * width / 2.0f; + pScreenPosition->y = ( -vecScreenPos.y + 1.0f ) * height / 2.0f; +} + + diff --git a/tier2/defaultfilesystem.cpp b/tier2/defaultfilesystem.cpp new file mode 100644 index 0000000..b3a1662 --- /dev/null +++ b/tier2/defaultfilesystem.cpp @@ -0,0 +1,57 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A higher level link library for general use in the game and tools. +// +//===========================================================================// + +#include +#include +#include + + +static CSysModule *g_pFullFileSystemModule = NULL; + +void* DefaultCreateInterfaceFn(const char *pName, int *pReturnCode) +{ + if ( pReturnCode ) + { + *pReturnCode = 0; + } + return NULL; +} + +void InitDefaultFileSystem( void ) +{ + AssertMsg( !g_pFullFileSystem, "Already set up the file system" ); + + if ( !Sys_LoadInterface( "filesystem_stdio", FILESYSTEM_INTERFACE_VERSION, + &g_pFullFileSystemModule, (void**)&g_pFullFileSystem ) ) + { + if ( !Sys_LoadInterface( "filesystem_steam", FILESYSTEM_INTERFACE_VERSION, + &g_pFullFileSystemModule, (void**)&g_pFullFileSystem ) ) + { + exit(0); + } + } + + if ( !g_pFullFileSystem->Connect( DefaultCreateInterfaceFn ) ) + { + exit(0); + } + + if ( g_pFullFileSystem->Init() != INIT_OK ) + { + exit(0); + } + + g_pFullFileSystem->RemoveAllSearchPaths(); + g_pFullFileSystem->AddSearchPath( "", "LOCAL", PATH_ADD_TO_HEAD ); +} + +void ShutdownDefaultFileSystem(void) +{ + AssertMsg( g_pFullFileSystem, "File system not set up" ); + g_pFullFileSystem->Shutdown(); + g_pFullFileSystem->Disconnect(); + Sys_UnloadModule( g_pFullFileSystemModule ); +} diff --git a/tier2/dmconnect.cpp b/tier2/dmconnect.cpp new file mode 100644 index 0000000..b8b41f1 --- /dev/null +++ b/tier2/dmconnect.cpp @@ -0,0 +1,62 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A higher level link library for general use in the game and tools. +// +//===========================================================================// + +#include +#include "datamodel/idatamodel.h" +#include "dmserializers/idmserializers.h" + + +//----------------------------------------------------------------------------- +// Set up methods related to datamodel interfaces +//----------------------------------------------------------------------------- +bool ConnectDataModel( CreateInterfaceFn factory ) +{ + if ( !g_pDataModel->Connect( factory ) ) + return false; + + if ( !g_pDmElementFramework->Connect( factory ) ) + return false; + + if ( !g_pDmSerializers->Connect( factory ) ) + return false; + + return true; +} + +InitReturnVal_t InitDataModel() +{ + InitReturnVal_t nRetVal; + + nRetVal = g_pDataModel->Init( ); + if ( nRetVal != INIT_OK ) + return nRetVal; + + nRetVal = g_pDmElementFramework->Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + nRetVal = g_pDmSerializers->Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + return INIT_OK; +} + +void ShutdownDataModel() +{ + g_pDmSerializers->Shutdown(); + g_pDmElementFramework->Shutdown(); + g_pDataModel->Shutdown( ); +} + +void DisconnectDataModel() +{ + g_pDmSerializers->Disconnect(); + g_pDmElementFramework->Disconnect(); + g_pDataModel->Disconnect(); +} + + diff --git a/tier2/fileutils.cpp b/tier2/fileutils.cpp new file mode 100644 index 0000000..2a0ca59 --- /dev/null +++ b/tier2/fileutils.cpp @@ -0,0 +1,304 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Helper methods + classes for file access +// +//===========================================================================// + +#include "tier2/fileutils.h" +#include "tier2/tier2.h" +#include "tier1/strtools.h" +#include "filesystem.h" +#include "tier0/icommandline.h" +#include "tier1/utlbuffer.h" + +// NOTE: This has to be the last file included! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Builds a directory which is a subdirectory of the current mod +//----------------------------------------------------------------------------- +void GetModSubdirectory( const char *pSubDir, char *pBuf, int nBufLen ) +{ + // Compute starting directory + Assert( g_pFullFileSystem->GetSearchPath( "MOD_WRITE", false, NULL, 0 ) < nBufLen ); + if ( g_pFullFileSystem->GetSearchPath( "MOD_WRITE", false, pBuf, nBufLen ) == 0 ) + { + // if we didn't find MOD_WRITE, back to the old MOD + Assert( g_pFullFileSystem->GetSearchPath( "MOD", false, NULL, 0 ) < nBufLen ); + g_pFullFileSystem->GetSearchPath( "MOD", false, pBuf, nBufLen ); + } + + char *pSemi = strchr( pBuf, ';' ); + if ( pSemi ) + { + *pSemi = 0; + } + + Q_StripTrailingSlash( pBuf ); + if ( pSubDir ) + { + int nLen = Q_strlen( pSubDir ); + Q_strncat( pBuf, "\\", nBufLen, 1 ); + Q_strncat( pBuf, pSubDir, nBufLen, nLen ); + } + + Q_FixSlashes( pBuf ); +} + + +//----------------------------------------------------------------------------- +// Builds a directory which is a subdirectory of the current mod's *content* +//----------------------------------------------------------------------------- +void GetModContentSubdirectory( const char *pSubDir, char *pBuf, int nBufLen ) +{ + char pTemp[ MAX_PATH ]; + GetModSubdirectory( pSubDir, pTemp, sizeof(pTemp) ); + ComputeModContentFilename( pTemp, pBuf, nBufLen ); +} + + +//----------------------------------------------------------------------------- +// Generates a filename under the 'game' subdirectory given a subdirectory of 'content' +//----------------------------------------------------------------------------- +void ComputeModFilename( const char *pContentFileName, char *pBuf, size_t nBufLen ) +{ + char pRelativePath[ MAX_PATH ]; + if ( !g_pFullFileSystem->FullPathToRelativePathEx( pContentFileName, "CONTENTROOT", pRelativePath, sizeof(pRelativePath) ) ) + { + Q_strncpy( pBuf, pContentFileName, (int)nBufLen ); + return; + } + + char pGameRoot[ MAX_PATH ]; + g_pFullFileSystem->GetSearchPath( "GAMEROOT", false, pGameRoot, sizeof(pGameRoot) ); + char *pSemi = strchr( pGameRoot, ';' ); + if ( pSemi ) + { + *pSemi = 0; + } + + Q_ComposeFileName( pGameRoot, pRelativePath, pBuf, (int)nBufLen ); +} + + +//----------------------------------------------------------------------------- +// Generates a filename under the 'content' subdirectory given a subdirectory of 'game' +//----------------------------------------------------------------------------- +void ComputeModContentFilename( const char *pGameFileName, char *pBuf, size_t nBufLen ) +{ + char pRelativePath[ MAX_PATH ]; + if ( !g_pFullFileSystem->FullPathToRelativePathEx( pGameFileName, "GAMEROOT", pRelativePath, sizeof(pRelativePath) ) ) + { + Q_strncpy( pBuf, pGameFileName, (int)nBufLen ); + return; + } + + char pContentRoot[ MAX_PATH ]; + g_pFullFileSystem->GetSearchPath( "CONTENTROOT", false, pContentRoot, sizeof(pContentRoot) ); + char *pSemi = strchr( pContentRoot, ';' ); + if ( pSemi ) + { + *pSemi = 0; + } + + Q_ComposeFileName( pContentRoot, pRelativePath, pBuf, (int)nBufLen ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Generates an Xbox 360 filename from a PC filename +//----------------------------------------------------------------------------- +char *CreateX360Filename( const char *pSourceName, char *pTargetName, int targetLen ) +{ + Q_StripExtension( pSourceName, pTargetName, targetLen ); + int idx = Q_strlen( pTargetName ); + + // restore extension + Q_snprintf( pTargetName, targetLen, "%s.360%s", pTargetName, &pSourceName[idx] ); + + return pTargetName; +} + +//----------------------------------------------------------------------------- +// Purpose: Generates a PC filename from a possible 360 name. +// Strips the .360. from filename.360.extension. +// Filenames might have multiple '.', need to be careful and only consider the +// last true extension. Complex filenames do occur: +// d:\foo\.\foo.dat +// d:\zip0.360.zip\foo.360.dat +// Returns source if no change needs to occur, othwerwise generates and +// returns target. +//----------------------------------------------------------------------------- +char *RestoreFilename( const char *pSourceName, char *pTargetName, int targetLen ) +{ + // find extension + // scan backward for '.', but not past a seperator + int end = V_strlen( pSourceName ) - 1; + while ( end > 0 && pSourceName[end] != '.' && !( pSourceName[end] == '\\' || pSourceName[end] == '/' ) ) + { + --end; + } + + if ( end >= 4 && pSourceName[end] == '.' && !V_strncmp( pSourceName + end - 4 , ".360", 4 ) ) + { + // cull the .360, leave the trailing extension + end -= 4; + int length = MIN( end + 1, targetLen ); + V_strncpy( pTargetName, pSourceName, length ); + V_strncat( pTargetName, pSourceName + end + 4, targetLen ); + + return pTargetName; + } + + // source filename is as expected + return (char *)pSourceName; +} + +//----------------------------------------------------------------------------- +// Generate an Xbox 360 file if it doesn't exist or is out of date. This function determines +// the source and target path and whether the file needs to be generated. The caller provides +// a callback function to do the actual creation of the 360 file. "pExtraData" is for the caller to +// pass the address of any data that the callback function may need to access. This function +// is ONLY to be called by caller's who expect to have 360 versions of their file. +//----------------------------------------------------------------------------- +int UpdateOrCreate( const char *pSourceName, char *pTargetName, int targetLen, const char *pPathID, CreateCallback_t pfnCreate, bool bForce, void *pExtraData ) +{ + if ( pTargetName ) + { + // caller could supply source as PC or 360 name, we want the PC filename + char szFixedSourceName[MAX_PATH]; + pSourceName = RestoreFilename( pSourceName, szFixedSourceName, sizeof( szFixedSourceName ) ); + // caller wants us to provide 360 named version of source + CreateX360Filename( pSourceName, pTargetName, targetLen ); + } + + // no conversion are performed by the game at runtime anymore + // SMB access was removed by the XDK for Vista.... + return UOC_NOT_CREATED; +} + + +//----------------------------------------------------------------------------- +// Returns the search path as a list of paths +//----------------------------------------------------------------------------- +void GetSearchPath( CUtlVector< CUtlString > &path, const char *pPathID ) +{ + int nMaxLen = g_pFullFileSystem->GetSearchPath( pPathID, false, NULL, 0 ); + char *pBuf = (char*)stackalloc( nMaxLen ); + g_pFullFileSystem->GetSearchPath( pPathID, false, pBuf, nMaxLen ); + + char *pSemi; + while ( NULL != ( pSemi = strchr( pBuf, ';' ) ) ) + { + *pSemi = 0; + path.AddToTail( pBuf ); + pBuf = pSemi + 1; + } + path.AddToTail( pBuf ); +} + +//----------------------------------------------------------------------------- +// Given file name in the current dir generate a full path to it. +//----------------------------------------------------------------------------- +bool GenerateFullPath( const char *pFileName, char const *pPathID, char *pBuf, int nBufLen ) +{ + if ( V_IsAbsolutePath( pFileName ) ) + { + V_strncpy( pBuf, pFileName, nBufLen ); + return true; + } + + const char *pFullPath = g_pFullFileSystem->RelativePathToFullPath( pFileName, pPathID, pBuf, nBufLen ); + if ( pFullPath && Q_IsAbsolutePath( pFullPath ) ) + return true; + + char pDir[ MAX_PATH ]; + if ( !g_pFullFileSystem->GetCurrentDirectory( pDir, sizeof( pDir ) ) ) + return false; + + V_ComposeFileName( pDir, pFileName, pBuf, nBufLen ); + V_RemoveDotSlashes( pBuf ); + return true; +} + +//----------------------------------------------------------------------------- +// Builds a list of all files under a directory with a particular extension +//----------------------------------------------------------------------------- +void AddFilesToList( CUtlVector< CUtlString > &list, const char *pDirectory, const char *pPathID, const char *pExtension ) +{ + char pSearchString[MAX_PATH]; + Q_snprintf( pSearchString, MAX_PATH, "%s\\*", pDirectory ); + + bool bIsAbsolute = Q_IsAbsolutePath( pDirectory ); + + // get the list of files + FileFindHandle_t hFind; + const char *pFoundFile = g_pFullFileSystem->FindFirstEx( pSearchString, pPathID, &hFind ); + + // add all the items + CUtlVector< CUtlString > subDirs; + for ( ; pFoundFile; pFoundFile = g_pFullFileSystem->FindNext( hFind ) ) + { + char pChildPath[MAX_PATH]; + Q_snprintf( pChildPath, MAX_PATH, "%s\\%s", pDirectory, pFoundFile ); + + if ( g_pFullFileSystem->FindIsDirectory( hFind ) ) + { + if ( Q_strnicmp( pFoundFile, ".", 2 ) && Q_strnicmp( pFoundFile, "..", 3 ) ) + { + subDirs.AddToTail( pChildPath ); + } + continue; + } + + // Check the extension matches + const char *pExt = Q_GetFileExtension( pFoundFile ); + if ( !pExt || Q_stricmp( pExt, pExtension ) != 0 ) + continue; + + char pFullPathBuf[MAX_PATH]; + char *pFullPath = pFullPathBuf; + if ( !bIsAbsolute ) + { + g_pFullFileSystem->RelativePathToFullPath( pChildPath, pPathID, pFullPathBuf, sizeof(pFullPathBuf) ); + } + else + { + pFullPath = pChildPath; + } + + V_strlower( pFullPath ); + Q_FixSlashes( pFullPath ); + list.AddToTail( pFullPath ); + } + + g_pFullFileSystem->FindClose( hFind ); + + int nCount = subDirs.Count(); + for ( int i = 0; i < nCount; ++i ) + { + AddFilesToList( list, subDirs[i], pPathID, pExtension ); + } +} + +void CBaseFile::ReadLines( CUtlStringList &lineList, int nMaxLineLength ) +{ + char *pLine = ( char * ) stackalloc( nMaxLineLength ); + while( ReadLine( pLine, nMaxLineLength ) ) + { + char *pEOL = strchr( pLine, '\n' ); // kill the \n + if ( pEOL ) + *pEOL = 0; + lineList.CopyAndAddToTail( pLine ); + } +} + +void CBaseFile::ReadFile( CUtlBuffer &fileData ) +{ + int nFileSize = Size(); + fileData.EnsureCapacity( Size() ); + int nSize = Read( fileData.Base(), nFileSize ); + fileData.SeekPut( CUtlBuffer::SEEK_HEAD, nSize ); +} + + diff --git a/tier2/keybindings.cpp b/tier2/keybindings.cpp new file mode 100644 index 0000000..d2c9257 --- /dev/null +++ b/tier2/keybindings.cpp @@ -0,0 +1,143 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include "tier2/keybindings.h" +#include "tier2/tier2.h" +#include "inputsystem/iinputsystem.h" +#include "tier1/utlbuffer.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + + +//----------------------------------------------------------------------------- +// Set a key binding +//----------------------------------------------------------------------------- +void CKeyBindings::SetBinding( ButtonCode_t code, const char *pBinding ) +{ + if ( code == BUTTON_CODE_INVALID || code == KEY_NONE ) + return; + + // free old bindings + if ( !m_KeyInfo[code].IsEmpty() ) + { + // Exactly the same, don't re-bind and fragment memory + if ( !Q_stricmp( m_KeyInfo[code], pBinding ) ) + return; + } + + // allocate memory for new binding + m_KeyInfo[code] = pBinding; +} + +void CKeyBindings::SetBinding( const char *pButtonName, const char *pBinding ) +{ + ButtonCode_t code = g_pInputSystem->StringToButtonCode( pButtonName ); + SetBinding( code, pBinding ); +} + +void CKeyBindings::Unbind( ButtonCode_t code ) +{ + if ( code != KEY_NONE && code != BUTTON_CODE_INVALID ) + { + m_KeyInfo[code] = ""; + } +} + +void CKeyBindings::Unbind( const char *pButtonName ) +{ + ButtonCode_t code = g_pInputSystem->StringToButtonCode( pButtonName ); + Unbind( code ); +} + +void CKeyBindings::UnbindAll() +{ + for ( int i = 0; i < BUTTON_CODE_LAST; i++ ) + { + m_KeyInfo[i] = ""; + } +} + + +//----------------------------------------------------------------------------- +// Count number of lines of bindings we'll be writing +//----------------------------------------------------------------------------- +int CKeyBindings::GetBindingCount( ) const +{ + int nCount = 0; + for ( int i = 0; i < BUTTON_CODE_LAST; i++ ) + { + if ( !m_KeyInfo[i].IsEmpty() ) + { + nCount++; + } + } + + return nCount; +} + + +//----------------------------------------------------------------------------- +// Writes lines containing "bind key value" +//----------------------------------------------------------------------------- +void CKeyBindings::WriteBindings( CUtlBuffer &buf ) +{ + for ( int i = 0; i < BUTTON_CODE_LAST; i++ ) + { + if ( !m_KeyInfo[i].IsEmpty() ) + { + const char *pButtonCode = g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ); + buf.Printf( "bind \"%s\" \"%s\"\n", pButtonCode, m_KeyInfo[i].Get() ); + } + } +} + + +//----------------------------------------------------------------------------- +// Returns the keyname to which a binding string is bound. E.g., if +// TAB is bound to +use then searching for +use will return "TAB" +//----------------------------------------------------------------------------- +const char *CKeyBindings::ButtonNameForBinding( const char *pBinding ) +{ + const char *pBind = pBinding; + if ( pBinding[0] == '+' ) + { + ++pBind; + } + + for ( int i = 0; i < BUTTON_CODE_LAST; i++ ) + { + if ( m_KeyInfo[i].IsEmpty() ) + continue; + + if ( m_KeyInfo[i][0] == '+' ) + { + if ( !Q_stricmp( &m_KeyInfo[i].Get()[1], pBind ) ) + return g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ); + } + else + { + if ( !Q_stricmp( m_KeyInfo[i], pBind ) ) + return g_pInputSystem->ButtonCodeToString( (ButtonCode_t)i ); + } + } + + return NULL; +} + +const char *CKeyBindings::GetBindingForButton( ButtonCode_t code ) +{ + if ( m_KeyInfo[code].IsEmpty() ) + return NULL; + + return m_KeyInfo[ code ]; +} + + + diff --git a/tier2/keyvaluesmacros.cpp b/tier2/keyvaluesmacros.cpp new file mode 100644 index 0000000..11a5c97 --- /dev/null +++ b/tier2/keyvaluesmacros.cpp @@ -0,0 +1,462 @@ +//===================== Copyright (c) Valve Corporation. All Rights Reserved. ====================== +// +//================================================================================================== + + +#include "filesystem.h" +#include "tier1/KeyValues.h" +#include "tier2/keyvaluesmacros.h" + + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//-------------------------------------------------------------------------------------------------- +// Returns true if the passed string matches the filename style glob, false otherwise +// * matches any characters, ? matches any single character, otherwise case insensitive matching +//-------------------------------------------------------------------------------------------------- +bool GlobMatch( const char *pszGlob, const char *pszString ) +{ + while ( ( *pszString != '\0' ) && ( *pszGlob != '*' ) ) + { + if ( ( V_strnicmp( pszGlob, pszString, 1 ) != 0 ) && ( *pszGlob != '?' ) ) + { + return false; + } + + ++pszGlob; + ++pszString; + } + + const char *pszGlobTmp = nullptr; + const char *pszStringTmp = nullptr; + + while ( *pszString ) + { + if ( *pszGlob == '*' ) + { + ++pszGlob; + + if ( *pszGlob == '\0' ) + { + return true; + } + + pszGlobTmp = pszGlob; + pszStringTmp = pszString + 1; + } + else if ( ( V_strnicmp( pszGlob, pszString, 1 ) == 0 ) || ( *pszGlob == '?' ) ) + { + ++pszGlob; + ++pszString; + } + else + { + pszGlob = pszGlobTmp; + pszString = pszStringTmp++; + } + } + + while ( *pszGlob == '*' ) + { + ++pszGlob; + } + + return *pszGlob == '\0'; +} + + +//-------------------------------------------------------------------------------------------------- +// Inserts pkvToInsert after pkvAfter but setting pkvAfter's NextKey to pkvInsert +//-------------------------------------------------------------------------------------------------- +static void InsertKeyValuesAfter( KeyValues *pkvAfter, KeyValues *pkvToInsert ) +{ + Assert( pkvAfter ); + Assert( pkvToInsert ); + + pkvToInsert->SetNextKey( pkvAfter->GetNextKey() ); + pkvAfter->SetNextKey( pkvToInsert ); +} + + +//-------------------------------------------------------------------------------------------------- +// +//-------------------------------------------------------------------------------------------------- +static KeyValues *ReplaceSubKeyWithCopy( KeyValues *pkvParent, KeyValues *pkvToReplace, KeyValues *pkvReplaceWith ) +{ + Assert( pkvReplaceWith->GetFirstSubKey() == nullptr ); + + KeyValues *pkvCopy = pkvReplaceWith->MakeCopy(); + Assert( pkvCopy->GetFirstSubKey() == nullptr ); + Assert( pkvCopy->GetNextKey() == nullptr ); + + InsertKeyValuesAfter( pkvToReplace, pkvCopy ); + pkvParent->RemoveSubKey( pkvToReplace ); + pkvToReplace->deleteThis(); + + return pkvCopy; +} + + +//-------------------------------------------------------------------------------------------------- +// Handles a KeyValues #insert macro. Replaces the #insert KeyValues with the specified file +// if it can be loaded. This is not called #include because base KeyValue's already has #include +// but it has two issues. The #include is relative to the keyvalues, these paths are resolved +// normally via IFileSystem and #include only works at the top level, #insert works no matter +// how deep the #insert macro is +//-------------------------------------------------------------------------------------------------- +static KeyValues *HandleKeyValuesMacro_Insert( KeyValues *pkvInsert, KeyValues *pkvParent ) +{ + const char *pszName = pkvInsert->GetName(); + + if ( V_stricmp( "#insert", pszName ) != 0 ) + return nullptr; + + // Have an #insert key + + if ( pkvInsert->GetFirstSubKey() ) + { + // Invalid, has sub keys + Msg( "Error: #insert on key with subkeys, can only do #insert with simple key/value with string value\n" ); + return nullptr; + } + + if ( pkvInsert->GetDataType() != KeyValues::TYPE_STRING ) + { + // Invalid, value isn't a string + Msg( "Error: #insert on key without a data type of string, can only do #insert with simple key/value with string value\n" ); + return nullptr; + } + + const char *pszInsert = pkvInsert->GetString(); + if ( !pszInsert && *pszInsert == '\0' ) + { + // Invalid, value is empty string + Msg( "Error: #insert on key with empty string value, can only do #insert with simple key/value with string value\n" ); + return nullptr; + } + + FileHandle_t f = g_pFullFileSystem->Open( pszInsert, "rb" ); + if ( !f ) + { + // Invalid, couldn't open #insert file + Msg( "Error: #insert couldn't open file: %s\n", pszInsert ); + return nullptr; + } + + uint nFileSize = g_pFullFileSystem->Size( f ); + if ( nFileSize == 0 ) + { + // Invalid, empty file + Msg( "Error: #insert empty file: %s\n", pszInsert ); + return nullptr; + } + + uint nBufSize = g_pFullFileSystem->GetOptimalReadSize( f, nFileSize + 2 /* null termination */ + 8 /* "\"x\"\n{\n}\n" */ ); + char *pBuf = ( char* )g_pFullFileSystem->AllocOptimalReadBuffer( f, nBufSize ); + pBuf[0] = '"'; + pBuf[1] = 'i'; + pBuf[2] = '"'; + pBuf[3] = '\n'; + pBuf[4] = '{'; + pBuf[5] = '\n'; + + bool bRetOK = ( g_pFullFileSystem->ReadEx( pBuf + 6, nBufSize - 6, nFileSize, f ) != 0 ); + + g_pFullFileSystem->Close( f ); + + KeyValues *pkvNew = nullptr; + + if ( bRetOK ) + { + pBuf[nFileSize + 6 + 0] = '}'; + pBuf[nFileSize + 6 + 1] = '\n'; + pBuf[nFileSize + 6 + 2] = '\0'; + pBuf[nFileSize + 6 + 3] = '\0'; // Double NULL termination + + pkvNew = new KeyValues( pszInsert ); + + bRetOK = pkvNew->LoadFromBuffer( pszInsert, pBuf, g_pFullFileSystem ); + } + else + { + Msg( "Error: #insert couldn't read file: %s\n", pszInsert ); + } + + g_pFullFileSystem->FreeOptimalReadBuffer( pBuf ); + + KeyValues *pkvReturn = nullptr; + + CUtlVector< KeyValues * > newKeyList; + + if ( bRetOK ) + { + KeyValues *pkvInsertAfter = pkvInsert; + + KeyValues *pkvNewSubKey = pkvNew->GetFirstSubKey(); + pkvReturn = pkvNewSubKey; + + while ( pkvNewSubKey ) + { + KeyValues *pkvNextNewSubKey = pkvNewSubKey->GetNextKey(); + + pkvNew->RemoveSubKey( pkvNewSubKey ); + + bool bFound = false; + + if ( pkvNewSubKey->GetFirstSubKey() == nullptr ) + { + for ( KeyValues *pkvChild = pkvParent->GetFirstSubKey(); pkvChild; pkvChild = pkvChild->GetNextKey() ) + { + if ( pkvChild == pkvInsert ) + continue; + + if ( pkvChild->GetNameSymbol() == pkvNewSubKey->GetNameSymbol() ) + { + bFound = true; + break; + } + } + } + + if ( !bFound ) + { + InsertKeyValuesAfter( pkvInsertAfter, pkvNewSubKey ); + + pkvInsertAfter = pkvNewSubKey; + + newKeyList.AddToTail( pkvNewSubKey ); + } + + pkvNewSubKey = pkvNextNewSubKey; + } + + pkvParent->RemoveSubKey( pkvInsert ); + pkvInsert->deleteThis(); + } + + if ( pkvNew ) + { + pkvNew->deleteThis(); + } + + for ( int i = 0; i < newKeyList.Count(); ++i ) + { + HandleKeyValuesMacros( pkvParent, newKeyList[i] ); + } + + return pkvReturn; +} + + +//----------------------------------------------------------------------------- +// Merge pkvSrc over pkvDst, adding any new keys from src to dst but overwriting +// existing keys in dst with keys with matching names from src +//----------------------------------------------------------------------------- +static void UpdateKeyValuesBlock( KeyValues *pkvDst, KeyValues *pkvUpdate ) +{ + Assert( pkvDst->GetFirstSubKey() ); + Assert( pkvUpdate->GetFirstSubKey() ); + + for ( KeyValues *pkvUpdateSubKey = pkvUpdate->GetFirstSubKey(); pkvUpdateSubKey; pkvUpdateSubKey = pkvUpdateSubKey->GetNextKey() ) + { + const int nSrcName = pkvUpdateSubKey->GetNameSymbol(); + + if ( pkvUpdateSubKey->GetFirstSubKey() ) + { + Msg( "Error: #update has a key with subkeys, only simple key/values are allowed for #update, skipping: %s\n", pkvUpdateSubKey->GetName() ); + continue; + } + + KeyValues *pkvNew = nullptr; + + // Check for an existing key with the same name + for ( KeyValues *pkvDstSubKey = pkvDst->GetFirstSubKey(); pkvDstSubKey; pkvDstSubKey = pkvDstSubKey->GetNextKey() ) + { + if ( pkvDstSubKey == pkvUpdate ) + continue; + + const int nDstName = pkvDstSubKey->GetNameSymbol(); + + if ( nSrcName == nDstName ) + { + pkvNew = ReplaceSubKeyWithCopy( pkvDst, pkvDstSubKey, pkvUpdateSubKey ); + break; + } + } + + if ( !pkvNew ) + { + // Didn't update an existing key, add a key + pkvNew = pkvUpdateSubKey->MakeCopy(); + pkvDst->AddSubKey( pkvNew ); // TODO: Perhaps add this right after the #update block? + } + + Assert( pkvNew ); + + // Do inserts right away + if ( !V_strcmp( pkvNew->GetName(), "#insert" ) ) + { + while ( pkvNew ) + { + pkvNew = HandleKeyValuesMacros( pkvNew, pkvDst ); + } + } + } +} + + +//-------------------------------------------------------------------------------------------------- +// Handle's #update macros +// +// An #update must be a KeyValue block with a KeyValue block as a parent. It will look at sibling +// KeyValue blocks which match an optional "#glob", or all sibling KeyValue blocks if no "#glob" is +// specified and will merge all of the #update block's subkeys into each sibling block. +// overwriting KeyValues if they already exist and adding new KeyValues if they don't. +// +// Example: +// +// Before: +// +// "example" +// { +// "wear_level_1" +// { +// "one" "one_val" +// "two" "two_val" +// } +// "wear_level_2" +// { +// "one" "one_val" +// "two" "two_val" +// } +// "subblock" +// { +// "one" "one_val" +// "two" "two_val" +// } +// "#update" +// { +// "#glob" "wear_level_*" +// "one" "updated_one_val" +// "three" "three_val" +// } +// } +// +// After: +// +// "example" +// { +// "wear_level_1" +// { +// "one" "updated_one_val" +// "two" "two_val" +// "three" "three_val" +// } +// "wear_level_2" +// { +// "one" "updated_one_val" +// "two" "two_val" +// "three" "three_val" +// } +// "subblock" +// { +// "one" "one_val" +// "two" "two_val" +// } +// } +// +//-------------------------------------------------------------------------------------------------- +static KeyValues *HandleKeyValuesMacro_Update( KeyValues *pkvUpdate, KeyValues *pkvParent, bool *pbDidUpdate ) +{ + const char *pszName = pkvUpdate->GetName(); + + if ( V_stricmp( "#update", pszName ) != 0 ) + return nullptr; + + // Have an #update key + + if ( pkvUpdate->GetFirstSubKey() == nullptr ) + { + // Invalid, has sub keys + Msg( "Error: #insert on key without subkeys, can only do #update with a key with subkeys\n" ); + return nullptr; + } + + if ( pkvUpdate->GetDataType() != KeyValues::TYPE_NONE ) + { + // Invalid, value isn't a TYPE_NONE + Msg( "Error: #update on key without a data type of NONE, can only do #update with a key with subkeys\n" ); + return nullptr; + } + + const char *pszGlob = nullptr; + + KeyValues *pkvGlob = pkvUpdate->FindKey( "#glob" ); + if ( !pkvGlob ) + { + pkvGlob = pkvUpdate->FindKey( "glob" ); + } + + if ( pkvGlob ) + { + pszGlob = pkvGlob->GetString( nullptr, nullptr ); + pkvUpdate->RemoveSubKey( pkvGlob ); + } + + for ( KeyValues *pkvParentSubKey = pkvParent->GetFirstSubKey(); pkvParentSubKey; pkvParentSubKey = pkvParentSubKey->GetNextKey() ) + { + if ( pkvParentSubKey == pkvUpdate ) + continue; + + if ( pszGlob && !GlobMatch( pszGlob, pkvParentSubKey->GetName() ) ) + continue; + + UpdateKeyValuesBlock( pkvParentSubKey, pkvUpdate ); + } + + KeyValues *pkvReturn = pkvUpdate->GetNextKey(); + + pkvParent->RemoveSubKey( pkvUpdate ); + pkvUpdate->deleteThis(); + + if ( pbDidUpdate ) + { + *pbDidUpdate = true; + } + + return pkvReturn; +} + + +//-------------------------------------------------------------------------------------------------- +// Main external extry point +//-------------------------------------------------------------------------------------------------- +KeyValues *HandleKeyValuesMacros( KeyValues *kv, KeyValues *pkvParent /* = nullptr */ ) +{ + KeyValues *pkvNextKey = HandleKeyValuesMacro_Insert( kv, pkvParent ); + if ( pkvNextKey ) + { + Assert( kv->GetFirstSubKey() == nullptr ); + + return pkvNextKey; + } + + bool bDidLocalUpdate = false; + pkvNextKey = HandleKeyValuesMacro_Update( kv, pkvParent, &bDidLocalUpdate ); + if ( bDidLocalUpdate ) + { + Assert( kv->GetFirstSubKey() != nullptr ); + + return pkvNextKey; + } + + KeyValues *pkvSub = kv->GetFirstSubKey(); + while ( pkvSub ) + { + pkvSub = HandleKeyValuesMacros( pkvSub, kv ); + } + + return kv->GetNextKey(); +} diff --git a/tier2/meshutils.cpp b/tier2/meshutils.cpp new file mode 100644 index 0000000..4aa0144 --- /dev/null +++ b/tier2/meshutils.cpp @@ -0,0 +1,104 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A set of utilities to render standard shapes +// +//===========================================================================// + +#include "tier2/meshutils.h" + + +//----------------------------------------------------------------------------- +// Helper methods to create various standard index buffer types +//----------------------------------------------------------------------------- +void GenerateSequentialIndexBuffer( unsigned short* pIndices, int nIndexCount, int nFirstVertex ) +{ + if ( !pIndices ) + return; + + // Format the sequential buffer + for ( int i = 0; i < nIndexCount; ++i ) + { + pIndices[i] = (unsigned short)( i + nFirstVertex ); + } +} + +void GenerateQuadIndexBuffer( unsigned short* pIndices, int nIndexCount, int nFirstVertex ) +{ + if ( !pIndices ) + return; + + // Format the quad buffer + int i; + int numQuads = nIndexCount / 6; + int baseVertex = nFirstVertex; + for ( i = 0; i < numQuads; ++i) + { + // Triangle 1 + pIndices[0] = (unsigned short)( baseVertex ); + pIndices[1] = (unsigned short)( baseVertex + 1 ); + pIndices[2] = (unsigned short)( baseVertex + 2 ); + + // Triangle 2 + pIndices[3] = (unsigned short)( baseVertex ); + pIndices[4] = (unsigned short)( baseVertex + 2 ); + pIndices[5] = (unsigned short)( baseVertex + 3 ); + + baseVertex += 4; + pIndices += 6; + } +} + +void GeneratePolygonIndexBuffer( unsigned short* pIndices, int nIndexCount, int nFirstVertex ) +{ + if ( !pIndices ) + return; + + int i; + int numPolygons = nIndexCount / 3; + for ( i = 0; i < numPolygons; ++i) + { + // Triangle 1 + pIndices[0] = (unsigned short)( nFirstVertex ); + pIndices[1] = (unsigned short)( nFirstVertex + i + 1 ); + pIndices[2] = (unsigned short)( nFirstVertex + i + 2 ); + pIndices += 3; + } +} + + +void GenerateLineStripIndexBuffer( unsigned short* pIndices, int nIndexCount, int nFirstVertex ) +{ + if ( !pIndices ) + return; + + int i; + int numLines = nIndexCount / 2; + for ( i = 0; i < numLines; ++i) + { + pIndices[0] = (unsigned short)( nFirstVertex + i ); + pIndices[1] = (unsigned short)( nFirstVertex + i + 1 ); + pIndices += 2; + } +} + +void GenerateLineLoopIndexBuffer( unsigned short* pIndices, int nIndexCount, int nFirstVertex ) +{ + if ( !pIndices ) + { + return; + } + + int i; + int numLines = nIndexCount / 2; + + pIndices[0] = (unsigned short)( nFirstVertex + numLines - 1 ); + pIndices[1] = (unsigned short)( nFirstVertex ); + pIndices += 2; + + for ( i = 1; i < numLines; ++i) + { + pIndices[0] = (unsigned short)( nFirstVertex + i - 1 ); + pIndices[1] = (unsigned short)( nFirstVertex + i ); + pIndices += 2; + } +} diff --git a/tier2/p4helpers.cpp b/tier2/p4helpers.cpp new file mode 100644 index 0000000..9b32dfb --- /dev/null +++ b/tier2/p4helpers.cpp @@ -0,0 +1,138 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "p4helpers.h" +#include "tier2/tier2.h" +#include "p4lib/ip4.h" + +#ifdef PLATFORM_WINDOWS_PC +#include +#endif // PLATFORM_WINDOWS_PC + +////////////////////////////////////////////////////////////////////////// +// +// CP4File implementation +// +////////////////////////////////////////////////////////////////////////// + +CP4File::CP4File( char const *szFilename ) +{ +#ifdef PLATFORM_WINDOWS_PC + + // On windows, get the pathname of the file on disk first before using that as a perforce path + // this avoids invalid Adds(). Have to go through GetShortPathName and then GetLongPathName from + // the short path name + + TCHAR szShortPathName[ MAX_PATH ] = TEXT( "" ); + const DWORD shortRetVal = GetShortPathName( szFilename, szShortPathName, ARRAYSIZE( szShortPathName ) ); + + if ( shortRetVal > 0 && shortRetVal <= ARRAYSIZE( szShortPathName ) ) + { + TCHAR szLongPathName[ MAX_PATH ] = TEXT( "" ); + + const DWORD longRetVal = GetLongPathName( szShortPathName, szLongPathName, ARRAYSIZE( szLongPathName ) ); + + if ( longRetVal > 0 && longRetVal <= ARRAYSIZE( szLongPathName ) ) + { + m_sFilename = szLongPathName; + return; + } + } + +#endif // PLATFORM_WINDOWS_PC + + m_sFilename = szFilename; +} + +CP4File::~CP4File() +{ +} + +bool CP4File::Edit( void ) +{ + if ( !p4 ) + return true; + + return p4->OpenFileForEdit( m_sFilename.String() ); +} + +bool CP4File::Add( void ) +{ + if ( !p4 ) + return true; + + return p4->OpenFileForAdd( m_sFilename.String() ); +} + +bool CP4File::Revert( void ) +{ + if ( !p4 ) + return true; + + return p4->RevertFile( m_sFilename.String() ); +} + +// Is the file in perforce? +bool CP4File::IsFileInPerforce() +{ + if ( !p4 ) + return false; + + return p4->IsFileInPerforce( m_sFilename.String() ); +} + +bool CP4File::SetFileType(const CUtlString& desiredFileType) +{ + if ( !p4 ) + return false; + + return p4->SetFileType( m_sFilename.String(), desiredFileType.String() ); +} + + +////////////////////////////////////////////////////////////////////////// +// +// CP4Factory implementation +// +////////////////////////////////////////////////////////////////////////// + + +CP4Factory::CP4Factory() +{ +} + +CP4Factory::~CP4Factory() +{ +} + +bool CP4Factory::SetDummyMode( bool bDummyMode ) +{ + bool bOld = m_bDummyMode; + m_bDummyMode = bDummyMode; + return bOld; +} + +void CP4Factory::SetOpenFileChangeList( const char *szChangeListName ) +{ + if ( !m_bDummyMode && p4 ) + p4->SetOpenFileChangeList( szChangeListName ); +} + +CP4File *CP4Factory::AccessFile( char const *szFilename ) const +{ + if ( !m_bDummyMode ) + return new CP4File( szFilename ); + else + return new CP4File_Dummy( szFilename ); +} + + +// Default p4 factory +static CP4Factory s_static_p4_factory; +CP4Factory *g_p4factory = &s_static_p4_factory; // NULL before the factory constructs + diff --git a/tier2/renderutils.cpp b/tier2/renderutils.cpp new file mode 100644 index 0000000..41b8bc6 --- /dev/null +++ b/tier2/renderutils.cpp @@ -0,0 +1,916 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A set of utilities to render standard shapes +// +//===========================================================================// + +#include "tier2/renderutils.h" +#include "tier2/tier2.h" +#include "tier1/KeyValues.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imaterial.h" +#include "tier0/vprof.h" +#include "tier0/basetypes.h" +#include "togl/rendermechanism.h" + +#if !defined(M_PI) + #define M_PI 3.14159265358979323846 +#endif + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +static bool s_bMaterialsInitialized = false; +static IMaterial *s_pWireframe; +static IMaterial *s_pWireframeIgnoreZ; +static IMaterial *s_pVertexColor; +static IMaterial *s_pVertexColorIgnoreZ; + + +//----------------------------------------------------------------------------- +// Initializes standard materials +//----------------------------------------------------------------------------- +void InitializeStandardMaterials() +{ + if ( s_bMaterialsInitialized ) + return; + + s_bMaterialsInitialized = true; + + KeyValues *pVMTKeyValues = new KeyValues( "wireframe" ); + pVMTKeyValues->SetInt( "$vertexcolor", 1 ); + s_pWireframe = g_pMaterialSystem->CreateMaterial( "__utilWireframe", pVMTKeyValues ); + + pVMTKeyValues = new KeyValues( "wireframe" ); + pVMTKeyValues->SetInt( "$vertexcolor", 1 ); + pVMTKeyValues->SetInt( "$vertexalpha", 1 ); + pVMTKeyValues->SetInt( "$ignorez", 1 ); + s_pWireframeIgnoreZ = g_pMaterialSystem->CreateMaterial( "__utilWireframeIgnoreZ", pVMTKeyValues ); + + pVMTKeyValues = new KeyValues( "unlitgeneric" ); + pVMTKeyValues->SetInt( "$vertexcolor", 1 ); + pVMTKeyValues->SetInt( "$vertexalpha", 1 ); + s_pVertexColor = g_pMaterialSystem->CreateMaterial( "__utilVertexColor", pVMTKeyValues ); + + pVMTKeyValues = new KeyValues( "unlitgeneric" ); + pVMTKeyValues->SetInt( "$vertexcolor", 1 ); + pVMTKeyValues->SetInt( "$vertexalpha", 1 ); + pVMTKeyValues->SetInt( "$ignorez", 1 ); + s_pVertexColorIgnoreZ = g_pMaterialSystem->CreateMaterial( "__utilVertexColorIgnoreZ", pVMTKeyValues ); +} + +void ShutdownStandardMaterials() +{ + if ( !s_bMaterialsInitialized ) + return; + + s_bMaterialsInitialized = false; + + s_pWireframe->DecrementReferenceCount(); + s_pWireframe = NULL; + + s_pWireframeIgnoreZ->DecrementReferenceCount(); + s_pWireframeIgnoreZ = NULL; + + s_pVertexColor->DecrementReferenceCount(); + s_pVertexColor = NULL; + + s_pVertexColorIgnoreZ->DecrementReferenceCount(); + s_pVertexColorIgnoreZ = NULL; +} + + +//----------------------------------------------------------------------------- +// Renders a wireframe sphere +//----------------------------------------------------------------------------- +void RenderWireframeSphere( const Vector &vCenter, float flRadius, int nTheta, int nPhi, Color c, bool bZBuffer ) +{ + InitializeStandardMaterials(); + + // Make one more coordinate because (u,v) is discontinuous. + ++nTheta; + + int nVertices = nPhi * nTheta; + int nIndices = ( nTheta - 1 ) * 4 * ( nPhi - 1 ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( bZBuffer ? s_pWireframe : s_pWireframeIgnoreZ ); + + CMeshBuilder meshBuilder; + IMesh* pMesh = pRenderContext->GetDynamicMesh(); + + meshBuilder.Begin( pMesh, MATERIAL_LINES, nVertices, nIndices ); + + unsigned char chRed = c.r(); + unsigned char chGreen = c.g(); + unsigned char chBlue = c.b(); + unsigned char chAlpha = c.a(); + + int i, j; + for ( i = 0; i < nPhi; ++i ) + { + for ( j = 0; j < nTheta; ++j ) + { + float u = j / ( float )( nTheta - 1 ); + float v = i / ( float )( nPhi - 1 ); + float theta = 2.0f * M_PI * u; + float phi = M_PI * v; + + meshBuilder.Position3f( vCenter.x + ( flRadius * sin(phi) * cos(theta) ), + vCenter.y + ( flRadius * sin(phi) * sin(theta) ), + vCenter.z + ( flRadius * cos(phi) ) ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.AdvanceVertex(); + } + } + + for ( i = 0; i < nPhi - 1; ++i ) + { + for ( j = 0; j < nTheta - 1; ++j ) + { + int idx = nTheta * i + j; + + meshBuilder.Index( idx ); + meshBuilder.AdvanceIndex(); + + meshBuilder.Index( idx + nTheta ); + meshBuilder.AdvanceIndex(); + + meshBuilder.Index( idx ); + meshBuilder.AdvanceIndex(); + + meshBuilder.Index( idx + 1 ); + meshBuilder.AdvanceIndex(); + } + } + + meshBuilder.End(); + pMesh->Draw(); +} + + +//----------------------------------------------------------------------------- +// Draws a sphere +//----------------------------------------------------------------------------- +void RenderSphere( const Vector &vCenter, float flRadius, int nTheta, int nPhi, Color c, IMaterial *pMaterial ) +{ + InitializeStandardMaterials(); + + CMatRenderContextPtr pRenderContext( materials ); + + unsigned char chRed = c.r(); + unsigned char chGreen = c.g(); + unsigned char chBlue = c.b(); + unsigned char chAlpha = c.a(); + + // Two extra degenerate triangles per row (except the last one) + int nTriangles = 2 * nTheta * ( nPhi - 1 ); + int nIndices = 2 * ( nTheta + 1 ) * ( nPhi - 1 ); + if ( nTriangles == 0 ) + return; + + pRenderContext->Bind( pMaterial ); + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + CMeshBuilder meshBuilder; + + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLE_STRIP, nTriangles, nIndices ); + + // Build the index buffer. + float flOONPhi = 1.0f / (nPhi-1); + float flOONTheta = 1.0f / (nTheta-1); + + int i, j; + for ( i = 0; i < nPhi; ++i ) + { + for ( j = 0; j < nTheta; ++j ) + { + float u = j / ( float )( nTheta - 1 ); + float v = i / ( float )( nPhi - 1 ); + float theta = 2.0f * M_PI * u; + float phi = M_PI * v; + + Vector vecPos; + vecPos.x = flRadius * sin(phi) * cos(theta); + vecPos.y = flRadius * sin(phi) * sin(theta); + vecPos.z = flRadius * cos(phi); + + Vector vecNormal = vecPos; + VectorNormalize(vecNormal); + + vecPos += vCenter; + + meshBuilder.Position3f( vecPos.x, vecPos.y, vecPos.z ); + meshBuilder.Normal3f( vecNormal.x, vecNormal.y, vecNormal.z ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.TexCoord2f( 0, j * flOONTheta, i * flOONPhi ); + meshBuilder.AdvanceVertex(); + } + } + + // Emit the triangle strips. + int idx = 0; + for ( i = 0; i < nPhi - 1; ++i ) + { + for ( j = 0; j < nTheta; ++j ) + { + idx = nTheta * i + j; + + meshBuilder.Index( idx + nTheta ); + meshBuilder.AdvanceIndex(); + + meshBuilder.Index( idx ); + meshBuilder.AdvanceIndex(); + } + + // Emit a degenerate triangle to skip to the next row without a connecting triangle + if ( i < nPhi - 2 ) + { + meshBuilder.Index( idx ); + meshBuilder.AdvanceIndex(); + + meshBuilder.Index( idx + nTheta + 1 ); + meshBuilder.AdvanceIndex(); + } + } + + meshBuilder.End(); + pMesh->Draw(); +} + +void RenderSphere( const Vector &vCenter, float flRadius, int nTheta, int nPhi, Color c, bool bZBuffer ) +{ + IMaterial *pMaterial = bZBuffer ? s_pVertexColor : s_pVertexColorIgnoreZ; + Color cActual( c.r(), c.g(), c.b(), c.a() ); + RenderSphere( vCenter, flRadius, nTheta, nPhi, cActual, pMaterial ); +} + + +//----------------------------------------------------------------------------- +// Box vertices +//----------------------------------------------------------------------------- +static int s_pBoxFaceIndices[6][4] = +{ + { 0, 4, 6, 2 }, // -x + { 5, 1, 3, 7 }, // +x + { 0, 1, 5, 4 }, // -y + { 2, 6, 7, 3 }, // +y + { 0, 2, 3, 1 }, // -z + { 4, 5, 7, 6 } // +z +}; + +static int s_pBoxFaceIndicesInsideOut[6][4] = +{ + { 0, 2, 6, 4 }, // -x + { 5, 7, 3, 1 }, // +x + { 0, 4, 5, 1 }, // -y + { 2, 3, 7, 6 }, // +y + { 0, 1, 3, 2 }, // -z + { 4, 6, 7, 5 } // +z +}; + +static void GenerateBoxVertices( const Vector &vOrigin, const QAngle& angles, const Vector &vMins, const Vector &vMaxs, Vector pVerts[8] ) +{ + // Build a rotation matrix from orientation + matrix3x4_t fRotateMatrix; + AngleMatrix( angles, fRotateMatrix ); + + Vector vecPos; + for ( int i = 0; i < 8; ++i ) + { + vecPos[0] = ( i & 0x1 ) ? vMaxs[0] : vMins[0]; + vecPos[1] = ( i & 0x2 ) ? vMaxs[1] : vMins[1]; + vecPos[2] = ( i & 0x4 ) ? vMaxs[2] : vMins[2]; + + VectorRotate( vecPos, fRotateMatrix, pVerts[i] ); + pVerts[i] += vOrigin; + } +} + + +//----------------------------------------------------------------------------- +// Renders a wireframe box relative to an origin +//----------------------------------------------------------------------------- +void RenderWireframeBox( const Vector &vOrigin, const QAngle& angles, const Vector &vMins, const Vector &vMaxs, Color c, bool bZBuffer ) +{ + InitializeStandardMaterials(); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( bZBuffer ? s_pWireframe : s_pWireframeIgnoreZ ); + + Vector p[8]; + GenerateBoxVertices( vOrigin, angles, vMins, vMaxs, p ); + + unsigned char chRed = c.r(); + unsigned char chGreen = c.g(); + unsigned char chBlue = c.b(); + unsigned char chAlpha = c.a(); + + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_LINES, 24 ); + + // Draw the box + for ( int i = 0; i < 6; i++ ) + { + int *pFaceIndex = s_pBoxFaceIndices[i]; + + for ( int j = 0; j < 4; ++j ) + { + meshBuilder.Position3fv( p[pFaceIndex[j]].Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( p[pFaceIndex[ (j == 3) ? 0 : j+1 ] ].Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.AdvanceVertex(); + } + } + + meshBuilder.End(); + pMesh->Draw(); +} + + +//----------------------------------------------------------------------------- +// Renders a solid box +//----------------------------------------------------------------------------- +void RenderBox( const Vector& vOrigin, const QAngle& angles, const Vector& vMins, const Vector& vMaxs, Color c, IMaterial *pMaterial, bool bInsideOut ) +{ + InitializeStandardMaterials(); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( pMaterial ); + + Vector p[8]; + GenerateBoxVertices( vOrigin, angles, vMins, vMaxs, p ); + + unsigned char chRed = c.r(); + unsigned char chGreen = c.g(); + unsigned char chBlue = c.b(); + unsigned char chAlpha = c.a(); + + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 12 ); + + // Draw the box + Vector vecNormal; + for ( int i = 0; i < 6; i++ ) + { + vecNormal.Init(); + vecNormal[ i/2 ] = ( i & 0x1 ) ? 1.0f : -1.0f; + + int *ppFaceIndices = bInsideOut ? s_pBoxFaceIndicesInsideOut[i] : s_pBoxFaceIndices[i]; + for ( int j = 1; j < 3; ++j ) + { + int i0 = ppFaceIndices[0]; + int i1 = ppFaceIndices[j]; + int i2 = ppFaceIndices[j+1]; + + meshBuilder.Position3fv( p[i0].Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.Normal3fv( vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( p[i2].Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.Normal3fv( vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, ( j == 1 ) ? 1.0f : 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( p[i1].Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.Normal3fv( vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, ( j == 1 ) ? 0.0f : 1.0f, 1.0f ); + meshBuilder.AdvanceVertex(); + } + } + + meshBuilder.End(); + pMesh->Draw(); +} + + +void RenderBox( const Vector& vOrigin, const QAngle& angles, const Vector& vMins, const Vector& vMaxs, Color c, bool bZBuffer, bool bInsideOut ) +{ + IMaterial *pMaterial = bZBuffer ? s_pVertexColor : s_pVertexColorIgnoreZ; + Color cActual( c.r(), c.g(), c.b(), c.a() ); + RenderBox( vOrigin, angles, vMins, vMaxs, cActual, pMaterial, bInsideOut ); +} + + +//----------------------------------------------------------------------------- +// Renders axes, red->x, green->y, blue->z +//----------------------------------------------------------------------------- +void RenderAxes( const Vector &vOrigin, float flScale, bool bZBuffer ) +{ + InitializeStandardMaterials(); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( bZBuffer ? s_pWireframe : s_pWireframeIgnoreZ ); + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_LINES, 3 ); + + meshBuilder.Position3f( vOrigin.x, vOrigin.y, vOrigin.z ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vOrigin.x + flScale, vOrigin.y, vOrigin.z ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vOrigin.x, vOrigin.y, vOrigin.z ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vOrigin.x, vOrigin.y + flScale, vOrigin.z ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vOrigin.x, vOrigin.y, vOrigin.z ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vOrigin.x, vOrigin.y, vOrigin.z + flScale ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + + +void RenderAxes( const matrix3x4_t &transform, float flScale, bool bZBuffer ) +{ + InitializeStandardMaterials(); + + Vector xAxis, yAxis, zAxis, vOrigin, temp; + MatrixGetColumn( transform, 0, xAxis ); + MatrixGetColumn( transform, 1, yAxis ); + MatrixGetColumn( transform, 2, zAxis ); + MatrixGetColumn( transform, 3, vOrigin ); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( bZBuffer ? s_pWireframe : s_pWireframeIgnoreZ ); + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_LINES, 3 ); + + meshBuilder.Position3fv( vOrigin.Base() ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + VectorMA( vOrigin, flScale, xAxis, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( 255, 0, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vOrigin.x, vOrigin.y, vOrigin.z ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + + VectorMA( vOrigin, flScale, yAxis, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( 0, 255, 0, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3f( vOrigin.x, vOrigin.y, vOrigin.z ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + VectorMA( vOrigin, flScale, zAxis, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( 0, 0, 255, 255 ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Render a line +//----------------------------------------------------------------------------- +void RenderLine( const Vector& v1, const Vector& v2, Color c, bool bZBuffer ) +{ + InitializeStandardMaterials(); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( bZBuffer ? s_pWireframe : s_pWireframeIgnoreZ ); + + unsigned char chRed = c.r(); + unsigned char chGreen = c.g(); + unsigned char chBlue = c.b(); + unsigned char chAlpha = c.a(); + + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_LINES, 1 ); + + meshBuilder.Position3fv( v1.Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( v2.Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + + +//----------------------------------------------------------------------------- +// Draws a triangle +//----------------------------------------------------------------------------- +void RenderTriangle( const Vector& p1, const Vector& p2, const Vector& p3, Color c, IMaterial *pMaterial ) +{ + InitializeStandardMaterials(); + + CMatRenderContextPtr pRenderContext( materials ); + pRenderContext->Bind( pMaterial ); + + unsigned char chRed = c.r(); + unsigned char chGreen = c.g(); + unsigned char chBlue = c.b(); + unsigned char chAlpha = c.a(); + + Vector vecNormal; + Vector vecDelta1, vecDelta2; + VectorSubtract( p2, p1, vecDelta1 ); + VectorSubtract( p3, p1, vecDelta2 ); + CrossProduct( vecDelta1, vecDelta2, vecNormal ); + VectorNormalize( vecNormal ); + + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, 1 ); + + meshBuilder.Position3fv( p1.Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.Normal3fv( vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( p2.Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.Normal3fv( vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, 0.0f, 1.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( p3.Base() ); + meshBuilder.Color4ub( chRed, chGreen, chBlue, chAlpha ); + meshBuilder.Normal3fv( vecNormal.Base() ); + meshBuilder.TexCoord2f( 0, 1.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + pMesh->Draw(); +} + + +void RenderTriangle( const Vector& p1, const Vector& p2, const Vector& p3, Color c, bool bZBuffer ) +{ + IMaterial *pMaterial = bZBuffer ? s_pVertexColor : s_pVertexColorIgnoreZ; + Color cActual( c.r(), c.g(), c.b(), c.a() ); + RenderTriangle( p1, p2, p3, cActual, pMaterial ); +} + + + +//----------------------------------------------------------------------------- +// Renders an extruded box +//----------------------------------------------------------------------------- +static void DrawAxes( const Vector& origin, Vector* pts, int idx, Color c, CMeshBuilder& meshBuilder ) +{ + Vector start, temp; + VectorAdd( pts[idx], origin, start ); + meshBuilder.Position3fv( start.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + int endidx = (idx & 0x1) ? idx - 1 : idx + 1; + VectorAdd( pts[endidx], origin, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( start.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + endidx = (idx & 0x2) ? idx - 2 : idx + 2; + VectorAdd( pts[endidx], origin, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( start.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + endidx = (idx & 0x4) ? idx - 4 : idx + 4; + VectorAdd( pts[endidx], origin, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); +} + +static void DrawExtrusionFace( const Vector& start, const Vector& end, + Vector* pts, int idx1, int idx2, Color c, CMeshBuilder& meshBuilder ) +{ + Vector temp; + VectorAdd( pts[idx1], start, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + VectorAdd( pts[idx2], start, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + VectorAdd( pts[idx2], end, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + VectorAdd( pts[idx1], end, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); + + VectorAdd( pts[idx1], start, temp ); + meshBuilder.Position3fv( temp.Base() ); + meshBuilder.Color4ub( c.r(), c.g(), c.b(), c.a() ); + meshBuilder.AdvanceVertex(); +} + +void RenderWireframeSweptBox( const Vector &vStart, const Vector &vEnd, const QAngle &angles, const Vector &vMins, const Vector &vMaxs, Color c, bool bZBuffer ) +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( bZBuffer ? s_pWireframe : s_pWireframeIgnoreZ ); + + Color cActual( c.r(), c.g(), c.b(), c.a() ); + + // Build a rotation matrix from angles + matrix3x4_t fRotateMatrix; + AngleMatrix( angles, fRotateMatrix ); + + IMesh *pMesh = pRenderContext->GetDynamicMesh( ); + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_LINES, 30 ); + + Vector vDelta; + VectorSubtract( vEnd, vStart, vDelta ); + + // Compute the box points, rotated but without the origin added + Vector temp; + Vector pts[8]; + float dot[8]; + int minidx = 0; + for ( int i = 0; i < 8; ++i ) + { + temp.x = (i & 0x1) ? vMaxs[0] : vMins[0]; + temp.y = (i & 0x2) ? vMaxs[1] : vMins[1]; + temp.z = (i & 0x4) ? vMaxs[2] : vMins[2]; + + // Rotate the corner point + VectorRotate( temp, fRotateMatrix, pts[i] ); + + // Find the dot product with dir + dot[i] = DotProduct( pts[i], vDelta ); + if ( dot[i] < dot[minidx] ) + { + minidx = i; + } + } + + // Choose opposite corner + int maxidx = minidx ^ 0x7; + + // Draw the start + end axes... + DrawAxes( vStart, pts, minidx, cActual, meshBuilder ); + DrawAxes( vEnd, pts, maxidx, cActual, meshBuilder ); + + // Draw the extrusion faces + for (int j = 0; j < 3; ++j ) + { + int dirflag1 = ( 1 << ((j+1)%3) ); + int dirflag2 = ( 1 << ((j+2)%3) ); + + int idx1, idx2, idx3; + idx1 = (minidx & dirflag1) ? minidx - dirflag1 : minidx + dirflag1; + idx2 = (minidx & dirflag2) ? minidx - dirflag2 : minidx + dirflag2; + idx3 = (minidx & dirflag2) ? idx1 - dirflag2 : idx1 + dirflag2; + + DrawExtrusionFace( vStart, vEnd, pts, idx1, idx3, cActual, meshBuilder ); + DrawExtrusionFace( vStart, vEnd, pts, idx2, idx3, cActual, meshBuilder ); + } + meshBuilder.End(); + pMesh->Draw(); +} + + +//----------------------------------------------------------------------------- +// Draws a axis-aligned quad +//----------------------------------------------------------------------------- +void RenderQuad( IMaterial *pMaterial, float x, float y, float w, float h, + float z, float s0, float t0, float s1, float t1, const Color& clr ) +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + IMesh *pMesh = pRenderContext->GetDynamicMesh( true, NULL, NULL, pMaterial ); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_QUADS, 1 ); + + meshBuilder.Color4ub( clr.r(), clr.g(), clr.b(), clr.a()); + meshBuilder.TexCoord2f( 0, s0, t0 ); + meshBuilder.Position3f( x, y, z ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ub( clr.r(), clr.g(), clr.b(), clr.a()); + meshBuilder.TexCoord2f( 0, s1, t0 ); + meshBuilder.Position3f( x + w, y, z ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ub( clr.r(), clr.g(), clr.b(), clr.a()); + meshBuilder.TexCoord2f( 0, s1, t1 ); + meshBuilder.Position3f( x + w, y + h, z ); + meshBuilder.AdvanceVertex(); + + meshBuilder.Color4ub( clr.r(), clr.g(), clr.b(), clr.a()); + meshBuilder.TexCoord2f( 0, s0, t1 ); + meshBuilder.Position3f( x, y + h, z ); + meshBuilder.AdvanceVertex(); + + meshBuilder.End(); + + pMesh->Draw(); +} + +//----------------------------------------------------------------------------- +// Renders a screen space quad +//----------------------------------------------------------------------------- + +void DrawScreenSpaceRectangle( IMaterial *pMaterial, + int nDestX, int nDestY, int nWidth, int nHeight, // Rect to draw into in screen space + float flSrcTextureX0, float flSrcTextureY0, // which texel you want to appear at destx/y + float flSrcTextureX1, float flSrcTextureY1, // which texel you want to appear at destx+width-1, desty+height-1 + int nSrcTextureWidth, int nSrcTextureHeight, // needed for fixup + void *pClientRenderable, // Used to pass to the bind proxies + int nXDice, int nYDice, // Amount to tessellate the mesh + float fDepth ) // what Z value to put in the verts (def 0.0) +{ + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + if ( ( nWidth <= 0 ) || ( nHeight <= 0 ) ) + return; + + tmZone( TELEMETRY_LEVEL1, TMZF_NONE, "%s", __FUNCTION__ ); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PushMatrix(); + pRenderContext->LoadIdentity(); + + pRenderContext->Bind( pMaterial, pClientRenderable ); + + int xSegments = max( nXDice, 1); + int ySegments = max( nYDice, 1); + + CMeshBuilder meshBuilder; + + IMesh* pMesh = pRenderContext->GetDynamicMesh( true ); + meshBuilder.Begin( pMesh, MATERIAL_QUADS, xSegments * ySegments ); + + int nScreenWidth, nScreenHeight; + pRenderContext->GetRenderTargetDimensions( nScreenWidth, nScreenHeight ); + + float flOffset = 0.5f; + + float flLeftX = nDestX - flOffset; + float flRightX = nDestX + nWidth - flOffset; + + float flTopY = nDestY - flOffset; + float flBottomY = nDestY + nHeight - flOffset; + + float flSubrectWidth = flSrcTextureX1 - flSrcTextureX0; + float flSubrectHeight = flSrcTextureY1 - flSrcTextureY0; + + float flTexelsPerPixelX = ( nWidth > 1 ) ? flSubrectWidth / ( nWidth - 1 ) : 0.0f; + float flTexelsPerPixelY = ( nHeight > 1 ) ? flSubrectHeight / ( nHeight - 1 ) : 0.0f; + + float flLeftU = flSrcTextureX0 + 0.5f - ( 0.5f * flTexelsPerPixelX ); + float flRightU = flSrcTextureX1 + 0.5f + ( 0.5f * flTexelsPerPixelX ); + float flTopV = flSrcTextureY0 + 0.5f - ( 0.5f * flTexelsPerPixelY ); + float flBottomV = flSrcTextureY1 + 0.5f + ( 0.5f * flTexelsPerPixelY ); + + float flOOTexWidth = 1.0f / nSrcTextureWidth; + float flOOTexHeight = 1.0f / nSrcTextureHeight; + flLeftU *= flOOTexWidth; + flRightU *= flOOTexWidth; + flTopV *= flOOTexHeight; + flBottomV *= flOOTexHeight; + + // Get the current viewport size + int vx, vy, vw, vh; + pRenderContext->GetViewport( vx, vy, vw, vh ); + + // map from screen pixel coords to -1..1 + flRightX = FLerp( -1, 1, 0, vw, flRightX ); + flLeftX = FLerp( -1, 1, 0, vw, flLeftX ); + flTopY = FLerp( 1, -1, 0, vh ,flTopY ); + flBottomY = FLerp( 1, -1, 0, vh, flBottomY ); + + // Dice the quad up... + if ( ( xSegments > 1 ) || ( ySegments > 1 ) ) + { + // Screen height and width of a subrect + float flWidth = (flRightX - flLeftX) / (float) xSegments; + float flHeight = (flTopY - flBottomY) / (float) ySegments; + + // UV height and width of a subrect + float flUWidth = (flRightU - flLeftU) / (float) xSegments; + float flVHeight = (flBottomV - flTopV) / (float) ySegments; + + for ( int x=0; x < xSegments; x++ ) + { + for ( int y=0; y < ySegments; y++ ) + { + // Top left + meshBuilder.Position3f( flLeftX + (float) x * flWidth, flTopY - (float) y * flHeight, fDepth ); + meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f ); + meshBuilder.TexCoord2f( 0, flLeftU + (float) x * flUWidth, flTopV + (float) y * flVHeight); + meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f ); + meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + // Top right (x+1) + meshBuilder.Position3f( flLeftX + (float) (x+1) * flWidth, flTopY - (float) y * flHeight, fDepth ); + meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f ); + meshBuilder.TexCoord2f( 0, flLeftU + (float) (x+1) * flUWidth, flTopV + (float) y * flVHeight); + meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f ); + meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + // Bottom right (x+1), (y+1) + meshBuilder.Position3f( flLeftX + (float) (x+1) * flWidth, flTopY - (float) (y+1) * flHeight, fDepth ); + meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f ); + meshBuilder.TexCoord2f( 0, flLeftU + (float) (x+1) * flUWidth, flTopV + (float)(y+1) * flVHeight); + meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f ); + meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + + // Bottom left (y+1) + meshBuilder.Position3f( flLeftX + (float) x * flWidth, flTopY - (float) (y+1) * flHeight, fDepth ); + meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f ); + meshBuilder.TexCoord2f( 0, flLeftU + (float) x * flUWidth, flTopV + (float)(y+1) * flVHeight); + meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f ); + meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + } + } + } + else // just one quad + { + for ( int corner=0; corner<4; corner++ ) + { + bool bLeft = (corner==0) || (corner==3); + meshBuilder.Position3f( (bLeft) ? flLeftX : flRightX, (corner & 2) ? flBottomY : flTopY, fDepth ); + meshBuilder.Normal3f( 0.0f, 0.0f, 1.0f ); + meshBuilder.TexCoord2f( 0, (bLeft) ? flLeftU : flRightU, (corner & 2) ? flBottomV : flTopV ); + meshBuilder.TangentS3f( 0.0f, 1.0f, 0.0f ); + meshBuilder.TangentT3f( 1.0f, 0.0f, 0.0f ); + meshBuilder.AdvanceVertex(); + } + } + + meshBuilder.End(); + pMesh->Draw(); + + pRenderContext->MatrixMode( MATERIAL_VIEW ); + pRenderContext->PopMatrix(); + + pRenderContext->MatrixMode( MATERIAL_PROJECTION ); + pRenderContext->PopMatrix(); +} diff --git a/tier2/riff.cpp b/tier2/riff.cpp new file mode 100644 index 0000000..9e36021 --- /dev/null +++ b/tier2/riff.cpp @@ -0,0 +1,506 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// Avoid these warnings: +#pragma warning(disable : 4512) // warning C4512: 'InFileRIFF' : assignment operator could not be generated +#pragma warning(disable : 4514) // warning C4514: 'RIFFName' : unreferenced inline function has been removed + +#include "riff.h" +#include +#include +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if 0 +//----------------------------------------------------------------------------- +// Purpose: Test code that implements the interface on stdio +//----------------------------------------------------------------------------- +class StdIOReadBinary : public IFileReadBinary +{ +public: + int open( const char *pFileName ) + { + return (int)fopen( pFileName, "rb" ); + } + + int read( void *pOutput, int size, int file ) + { + FILE *fp = (FILE *)file; + + return fread( pOutput, size, 1, fp ); + } + + void seek( int file, int pos ) + { + fseek( (FILE *)file, pos, SEEK_SET ); + } + + unsigned int tell( int file ) + { + return ftell( (FILE *)file ); + } + + unsigned int size( int file ) + { + FILE *fp = (FILE *)file; + if ( !fp ) + return 0; + + unsigned int pos = ftell( fp ); + fseek( fp, 0, SEEK_END ); + unsigned int size = ftell( fp ); + + fseek( fp, pos, SEEK_SET ); + return size; + } + + void close( int file ) + { + FILE *fp = (FILE *)file; + + fclose( fp ); + } +}; +#endif + + +#define RIFF_ID MAKEID('R','I','F','F') + + +//----------------------------------------------------------------------------- +// Purpose: Opens a RIFF file using the given I/O mechanism +// Input : *pFileName +// &io - I/O interface +//----------------------------------------------------------------------------- +InFileRIFF::InFileRIFF( const char *pFileName, IFileReadBinary &io ) : m_io(io) +{ + m_file = m_io.open( pFileName ); + + int riff = 0; + if ( !m_file ) + { + m_riffSize = 0; + m_riffName = 0; + return; + } + + riff = ReadInt(); + if ( riff != RIFF_ID ) + { + printf( "Not a RIFF File [%s]\n", pFileName ); + m_riffSize = 0; + } + else + { + // we store size as size of all chunks + // subtract off the RIFF form type (e.g. 'WAVE', 4 bytes) + m_riffSize = ReadInt() - 4; + m_riffName = ReadInt(); + + // HACKHACK: LWV files don't obey the RIFF format!!! + // Do this or miss the linguistic chunks at the end. Lame! + // subtract off 12 bytes for (RIFF, size, WAVE) + m_riffSize = m_io.size( m_file ) - 12; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Close the file +//----------------------------------------------------------------------------- +InFileRIFF::~InFileRIFF( void ) +{ + m_io.close( m_file ); +} + + +//----------------------------------------------------------------------------- +// Purpose: read a 4-byte int out of the stream +// Output : int = read value, default is zero +//----------------------------------------------------------------------------- +int InFileRIFF::ReadInt( void ) +{ + int tmp = 0; + m_io.read( &tmp, sizeof(int), m_file ); + tmp = LittleLong( tmp ); + + return tmp; +} + +//----------------------------------------------------------------------------- +// Purpose: Read a block of binary data +// Input : *pOutput - pointer to destination memory +// dataSize - size of block to read +// Output : int - number of bytes read +//----------------------------------------------------------------------------- +int InFileRIFF::ReadData( void *pOutput, int dataSize ) +{ + int count = m_io.read( pOutput, dataSize, m_file ); + + return count; +} + + +//----------------------------------------------------------------------------- +// Purpose: Gets the file position +// Output : int (bytes from start of file) +//----------------------------------------------------------------------------- +int InFileRIFF::PositionGet( void ) +{ + return m_io.tell( m_file ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Seek to file position +// Input : position - bytes from start of file +//----------------------------------------------------------------------------- +void InFileRIFF::PositionSet( int position ) +{ + m_io.seek( m_file, position ); +} + +//----------------------------------------------------------------------------- +// Purpose: Used to write a RIFF format file +//----------------------------------------------------------------------------- +OutFileRIFF::OutFileRIFF( const char *pFileName, IFileWriteBinary &io ) : m_io( io ) +{ + m_file = m_io.create( pFileName ); + + if ( !m_file ) + return; + + int riff = RIFF_ID; + m_io.write( &riff, 4, m_file ); + + m_riffSize = 0; + m_nNamePos = m_io.tell( m_file ); + + // Save room for the size and name now + WriteInt( 0 ); + + // Write out the name + WriteInt( RIFF_WAVE ); + + m_bUseIncorrectLISETLength = false; + m_nLISETSize = 0; +} + +OutFileRIFF::~OutFileRIFF( void ) +{ + if ( !IsValid() ) + return; + + unsigned int size = m_io.tell( m_file ) -8; + m_io.seek( m_file, m_nNamePos ); + + if ( m_bUseIncorrectLISETLength ) + { + size = m_nLISETSize - 8; + } + + WriteInt( size ); + m_io.close( m_file ); +} + +void OutFileRIFF::HasLISETData( int position ) +{ + m_bUseIncorrectLISETLength = true; + m_nLISETSize = position; +} + +bool OutFileRIFF::WriteInt( int number ) +{ + if ( !IsValid() ) + return false; + + m_io.write( &number, sizeof( int ), m_file ); + return true; +} + +bool OutFileRIFF::WriteData( void *pOutput, int dataSize ) +{ + if ( !IsValid() ) + return false; + + m_io.write( pOutput, dataSize, m_file ); + return true; +} + +int OutFileRIFF::PositionGet( void ) +{ + if ( !IsValid() ) + return 0; + + return m_io.tell( m_file ); +} + +void OutFileRIFF::PositionSet( int position ) +{ + if ( !IsValid() ) + return; + + m_io.seek( m_file, position ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Create an iterator for the given file +// Input : &riff - riff file +// size - size of file or sub-chunk +//----------------------------------------------------------------------------- +IterateRIFF::IterateRIFF( InFileRIFF &riff, int size ) + : m_riff(riff), m_size(size) +{ + if ( !m_riff.RIFFSize() ) + { + // bad file, just be an empty iterator + ChunkClear(); + return; + } + + // get the position and parse a chunk + m_start = riff.PositionGet(); + ChunkSetup(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Set up a sub-chunk iterator +// Input : &parent - parent iterator +//----------------------------------------------------------------------------- +IterateRIFF::IterateRIFF( IterateRIFF &parent ) + : m_riff(parent.m_riff), m_size(parent.ChunkSize()) +{ + m_start = parent.ChunkFilePosition(); + ChunkSetup(); +} + +//----------------------------------------------------------------------------- +// Purpose: Parse the chunk at the current file position +// This object will iterate over the sub-chunks of this chunk. +// This makes for easy hierarchical parsing +//----------------------------------------------------------------------------- +void IterateRIFF::ChunkSetup( void ) +{ + m_chunkPosition = m_riff.PositionGet(); + + m_chunkName = m_riff.ReadInt(); + m_chunkSize = m_riff.ReadInt(); +} + +//----------------------------------------------------------------------------- +// Purpose: clear chunk setup, ChunkAvailable will return false +//----------------------------------------------------------------------------- +void IterateRIFF::ChunkClear( void ) +{ + m_chunkSize = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: If there are chunks left to read beyond this one, return true +//----------------------------------------------------------------------------- +bool IterateRIFF::ChunkAvailable( void ) +{ + if ( m_chunkSize != -1 && m_chunkSize < 0x10000000 ) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: Go to the next chunk in the file, return true if there is one. +//----------------------------------------------------------------------------- +bool IterateRIFF::ChunkNext( void ) +{ + if ( !ChunkAvailable() ) + return false; + + int nextPos = m_chunkPosition + 8 + m_chunkSize; + + // chunks are aligned + nextPos += m_chunkSize & 1; + + if ( nextPos >= (m_start + m_size) ) + { + ChunkClear(); + return false; + } + + m_riff.PositionSet( nextPos ); + + ChunkSetup(); + return ChunkAvailable(); + +} + + +//----------------------------------------------------------------------------- +// Purpose: get the chunk FOURCC as an int +// Output : unsigned int +//----------------------------------------------------------------------------- +unsigned int IterateRIFF::ChunkName( void ) +{ + return m_chunkName; +} + + +//----------------------------------------------------------------------------- +// Purpose: get the size of this chunk +// Output : unsigned int +//----------------------------------------------------------------------------- +unsigned int IterateRIFF::ChunkSize( void ) +{ + return m_chunkSize; +} + +//----------------------------------------------------------------------------- +// Purpose: Read the entire chunk into a buffer +// Input : *pOutput - dest buffer +// Output : int bytes read +//----------------------------------------------------------------------------- +int IterateRIFF::ChunkRead( void *pOutput ) +{ + return m_riff.ReadData( pOutput, ChunkSize() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Read a partial chunk (updates file position for subsequent partial reads). +// Input : *pOutput - dest buffer +// dataSize - partial size +// Output : int - bytes read +//----------------------------------------------------------------------------- +int IterateRIFF::ChunkReadPartial( void *pOutput, int dataSize ) +{ + return m_riff.ReadData( pOutput, dataSize ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Read a 4-byte int +// Output : int - read int +//----------------------------------------------------------------------------- +int IterateRIFF::ChunkReadInt( void ) +{ + return m_riff.ReadInt(); +} + +//----------------------------------------------------------------------------- +// Purpose: Used to iterate over an InFileRIFF +//----------------------------------------------------------------------------- +IterateOutputRIFF::IterateOutputRIFF( OutFileRIFF &riff ) +: m_riff( riff ) +{ + if ( !m_riff.IsValid() ) + return; + + m_start = m_riff.PositionGet(); + m_chunkPosition = m_start; + m_chunkStart = -1; +} + +IterateOutputRIFF::IterateOutputRIFF( IterateOutputRIFF &parent ) + : m_riff(parent.m_riff) +{ + m_start = parent.ChunkFilePosition(); + m_chunkPosition = m_start; + m_chunkStart = -1; +} + +void IterateOutputRIFF::ChunkWrite( unsigned int chunkname, void *pOutput, int size ) +{ + m_chunkPosition = m_riff.PositionGet(); + + m_chunkName = chunkname; + m_chunkSize = size; + + m_riff.WriteInt( chunkname ); + m_riff.WriteInt( size ); + m_riff.WriteData( pOutput, size ); + + m_chunkPosition = m_riff.PositionGet(); + + m_chunkPosition += m_chunkPosition & 1; + + m_riff.PositionSet( m_chunkPosition ); + + m_chunkStart = -1; +} + +void IterateOutputRIFF::ChunkWriteInt( int number ) +{ + m_riff.WriteInt( number ); +} + +void IterateOutputRIFF::ChunkWriteData( void *pOutput, int size ) +{ + m_riff.WriteData( pOutput, size ); +} + +void IterateOutputRIFF::ChunkFinish( void ) +{ + Assert( m_chunkStart != -1 ); + + m_chunkPosition = m_riff.PositionGet(); + + int size = m_chunkPosition - m_chunkStart - 8; + + m_chunkPosition += m_chunkPosition & 1; + + m_riff.PositionSet( m_chunkStart + sizeof( int ) ); + + m_riff.WriteInt( size ); + + m_riff.PositionSet( m_chunkPosition ); + + m_chunkStart = -1; +} + +void IterateOutputRIFF::ChunkStart( unsigned int chunkname ) +{ + Assert( m_chunkStart == -1 ); + + m_chunkStart = m_riff.PositionGet(); + + m_riff.WriteInt( chunkname ); + m_riff.WriteInt( 0 ); +} + +void IterateOutputRIFF::ChunkSetPosition( int position ) +{ + m_riff.PositionSet( position ); +} + +unsigned int IterateOutputRIFF::ChunkGetPosition( void ) +{ + return m_riff.PositionGet(); +} + +void IterateOutputRIFF::CopyChunkData( IterateRIFF& input ) +{ + if ( input.ChunkSize() > 0 ) + { + char *buffer = new char[ input.ChunkSize() ]; + Assert( buffer ); + + input.ChunkRead( buffer ); + + // Don't copy/write the name or size, just the data itself + ChunkWriteData( buffer, input.ChunkSize() ); + + delete[] buffer; + } +} + +void IterateOutputRIFF::SetLISETData( int position ) +{ + m_riff.HasLISETData( position ); +} \ No newline at end of file diff --git a/tier2/soundutils.cpp b/tier2/soundutils.cpp new file mode 100644 index 0000000..bebda85 --- /dev/null +++ b/tier2/soundutils.cpp @@ -0,0 +1,237 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Helper methods + classes for sound +// +//===========================================================================// + +#include "tier2/soundutils.h" +#include "tier2/riff.h" +#include "tier2/tier2.h" +#include "filesystem.h" + +#ifdef IS_WINDOWS_PC + +#include // WAVEFORMATEX, WAVEFORMAT and ADPCM WAVEFORMAT!!! +#include + +#else + +#ifdef _X360 +#include "xbox/xbox_win32stubs.h" // WAVEFORMATEX, WAVEFORMAT and ADPCM WAVEFORMAT!!! +#endif + +#endif + +//----------------------------------------------------------------------------- +// RIFF reader/writers that use the file system +//----------------------------------------------------------------------------- +class CFSIOReadBinary : public IFileReadBinary +{ +public: + // inherited from IFileReadBinary + virtual int open( const char *pFileName ); + virtual int read( void *pOutput, int size, int file ); + virtual void seek( int file, int pos ); + virtual unsigned int tell( int file ); + virtual unsigned int size( int file ); + virtual void close( int file ); +}; + +class CFSIOWriteBinary : public IFileWriteBinary +{ +public: + virtual int create( const char *pFileName ); + virtual int write( void *pData, int size, int file ); + virtual void close( int file ); + virtual void seek( int file, int pos ); + virtual unsigned int tell( int file ); +}; + + +//----------------------------------------------------------------------------- +// Singletons +//----------------------------------------------------------------------------- +static CFSIOReadBinary s_FSIoIn; +static CFSIOWriteBinary s_FSIoOut; + +IFileReadBinary *g_pFSIOReadBinary = &s_FSIoIn; +IFileWriteBinary *g_pFSIOWriteBinary = &s_FSIoOut; + + +//----------------------------------------------------------------------------- +// RIFF reader that use the file system +//----------------------------------------------------------------------------- +int CFSIOReadBinary::open( const char *pFileName ) +{ + return (int)g_pFullFileSystem->Open( pFileName, "rb" ); +} + +int CFSIOReadBinary::read( void *pOutput, int size, int file ) +{ + if ( !file ) + return 0; + + return g_pFullFileSystem->Read( pOutput, size, (FileHandle_t)file ); +} + +void CFSIOReadBinary::seek( int file, int pos ) +{ + if ( !file ) + return; + + g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); +} + +unsigned int CFSIOReadBinary::tell( int file ) +{ + if ( !file ) + return 0; + + return g_pFullFileSystem->Tell( (FileHandle_t)file ); +} + +unsigned int CFSIOReadBinary::size( int file ) +{ + if ( !file ) + return 0; + + return g_pFullFileSystem->Size( (FileHandle_t)file ); +} + +void CFSIOReadBinary::close( int file ) +{ + if ( !file ) + return; + + g_pFullFileSystem->Close( (FileHandle_t)file ); +} + + +//----------------------------------------------------------------------------- +// RIFF writer that use the file system +//----------------------------------------------------------------------------- +int CFSIOWriteBinary::create( const char *pFileName ) +{ + g_pFullFileSystem->SetFileWritable( pFileName, true ); + return (int)g_pFullFileSystem->Open( pFileName, "wb" ); +} + +int CFSIOWriteBinary::write( void *pData, int size, int file ) +{ + return g_pFullFileSystem->Write( pData, size, (FileHandle_t)file ); +} + +void CFSIOWriteBinary::close( int file ) +{ + g_pFullFileSystem->Close( (FileHandle_t)file ); +} + +void CFSIOWriteBinary::seek( int file, int pos ) +{ + g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); +} + +unsigned int CFSIOWriteBinary::tell( int file ) +{ + return g_pFullFileSystem->Tell( (FileHandle_t)file ); +} + + +#ifndef POSIX +//----------------------------------------------------------------------------- +// Returns the duration of a wav file +//----------------------------------------------------------------------------- +float GetWavSoundDuration( const char *pWavFile ) +{ + InFileRIFF riff( pWavFile, *g_pFSIOReadBinary ); + + // UNDONE: Don't use printf to handle errors + if ( riff.RIFFName() != RIFF_WAVE ) + return 0.0f; + + int nDataSize = 0; + + // set up the iterator for the whole file (root RIFF is a chunk) + IterateRIFF walk( riff, riff.RIFFSize() ); + + // This chunk must be first as it contains the wave's format + // break out when we've parsed it + char pFormatBuffer[ 1024 ]; + int nFormatSize; + bool bFound = false; + for ( ; walk.ChunkAvailable( ); walk.ChunkNext() ) + { + switch ( walk.ChunkName() ) + { + case WAVE_FMT: + bFound = true; + if ( walk.ChunkSize() > sizeof(pFormatBuffer) ) + { + Warning( "oops, format tag too big!!!" ); + return 0.0f; + } + + nFormatSize = walk.ChunkSize(); + walk.ChunkRead( pFormatBuffer ); + break; + + case WAVE_DATA: + nDataSize += walk.ChunkSize(); + break; + } + } + + if ( !bFound ) + return 0.0f; + + const WAVEFORMATEX *pHeader = (const WAVEFORMATEX *)pFormatBuffer; + + int format = pHeader->wFormatTag; + + int nBits = pHeader->wBitsPerSample; + int nRate = pHeader->nSamplesPerSec; + int nChannels = pHeader->nChannels; + int nSampleSize = ( nBits * nChannels ) / 8; + + // this can never be zero -- other functions divide by this. + // This should never happen, but avoid crashing + if ( nSampleSize <= 0 ) + { + nSampleSize = 1; + } + + int nSampleCount = 0; + float flTrueSampleSize = nSampleSize; + + if ( format == WAVE_FORMAT_ADPCM ) + { + nSampleSize = 1; + + ADPCMWAVEFORMAT *pFormat = (ADPCMWAVEFORMAT *)pFormatBuffer; + int blockSize = ((pFormat->wSamplesPerBlock - 2) * pFormat->wfx.nChannels ) / 2; + blockSize += 7 * pFormat->wfx.nChannels; + + int blockCount = nSampleCount / blockSize; + int blockRem = nSampleCount % blockSize; + + // total samples in complete blocks + nSampleCount = blockCount * pFormat->wSamplesPerBlock; + + // add remaining in a short block + if ( blockRem ) + { + nSampleCount += pFormat->wSamplesPerBlock - (((blockSize - blockRem) * 2) / nChannels); + } + + flTrueSampleSize = 0.5f; + + } + else + { + nSampleCount = nDataSize / nSampleSize; + } + + float flDuration = (float)nSampleCount / (float)nRate; + return flDuration; +} +#endif diff --git a/tier2/tier2.cpp b/tier2/tier2.cpp new file mode 100644 index 0000000..7fd6143 --- /dev/null +++ b/tier2/tier2.cpp @@ -0,0 +1,117 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A higher level link library for general use in the game and tools. +// +//===========================================================================// + +#include +#include "tier0/dbg.h" +#include "filesystem.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "materialsystem/IColorCorrection.h" +#include "materialsystem/idebugtextureinfo.h" +#include "materialsystem/ivballoctracker.h" +#include "inputsystem/iinputsystem.h" +#include "networksystem/inetworksystem.h" +#include "p4lib/ip4.h" +#include "mdllib/mdllib.h" +#include "filesystem/IQueuedLoader.h" + + +//----------------------------------------------------------------------------- +// These tier2 libraries must be set by any users of this library. +// They can be set by calling ConnectTier2Libraries or InitDefaultFileSystem. +// It is hoped that setting this, and using this library will be the common mechanism for +// allowing link libraries to access tier2 library interfaces +//----------------------------------------------------------------------------- +IFileSystem *g_pFullFileSystem = 0; +IMaterialSystem *materials = 0; +IMaterialSystem *g_pMaterialSystem = 0; +IInputSystem *g_pInputSystem = 0; +INetworkSystem *g_pNetworkSystem = 0; +IMaterialSystemHardwareConfig *g_pMaterialSystemHardwareConfig = 0; +IDebugTextureInfo *g_pMaterialSystemDebugTextureInfo = 0; +IVBAllocTracker *g_VBAllocTracker = 0; +IColorCorrectionSystem *colorcorrection = 0; +IP4 *p4 = 0; +IMdlLib *mdllib = 0; +IQueuedLoader *g_pQueuedLoader = 0; + + +//----------------------------------------------------------------------------- +// Call this to connect to all tier 2 libraries. +// It's up to the caller to check the globals it cares about to see if ones are missing +//----------------------------------------------------------------------------- +void ConnectTier2Libraries( CreateInterfaceFn *pFactoryList, int nFactoryCount ) +{ + // Don't connect twice.. + Assert( !g_pFullFileSystem && !materials && !g_pInputSystem && !g_pNetworkSystem && + !p4 && !mdllib && !g_pMaterialSystemDebugTextureInfo && !g_VBAllocTracker && + !g_pMaterialSystemHardwareConfig && !g_pQueuedLoader ); + + for ( int i = 0; i < nFactoryCount; ++i ) + { + if ( !g_pFullFileSystem ) + { + g_pFullFileSystem = ( IFileSystem * )pFactoryList[i]( FILESYSTEM_INTERFACE_VERSION, NULL ); + } + if ( !materials ) + { + g_pMaterialSystem = materials = ( IMaterialSystem * )pFactoryList[i]( MATERIAL_SYSTEM_INTERFACE_VERSION, NULL ); + } + if ( !g_pInputSystem ) + { + g_pInputSystem = ( IInputSystem * )pFactoryList[i]( INPUTSYSTEM_INTERFACE_VERSION, NULL ); + } + if ( !g_pNetworkSystem ) + { + g_pNetworkSystem = ( INetworkSystem * )pFactoryList[i]( NETWORKSYSTEM_INTERFACE_VERSION, NULL ); + } + if ( !g_pMaterialSystemHardwareConfig ) + { + g_pMaterialSystemHardwareConfig = ( IMaterialSystemHardwareConfig * )pFactoryList[i]( MATERIALSYSTEM_HARDWARECONFIG_INTERFACE_VERSION, NULL ); + } + if ( !g_pMaterialSystemDebugTextureInfo ) + { + g_pMaterialSystemDebugTextureInfo = (IDebugTextureInfo*)pFactoryList[i]( DEBUG_TEXTURE_INFO_VERSION, 0 ); + } + if ( !g_VBAllocTracker ) + { + g_VBAllocTracker = (IVBAllocTracker*)pFactoryList[i]( VB_ALLOC_TRACKER_INTERFACE_VERSION, 0 ); + } + if ( !colorcorrection ) + { + colorcorrection = ( IColorCorrectionSystem * )pFactoryList[i]( COLORCORRECTION_INTERFACE_VERSION, NULL ); + } + if ( !p4 ) + { + p4 = ( IP4 * )pFactoryList[i]( P4_INTERFACE_VERSION, NULL ); + } + if ( !mdllib ) + { + mdllib = ( IMdlLib * )pFactoryList[i]( MDLLIB_INTERFACE_VERSION, NULL ); + } + if ( !g_pQueuedLoader ) + { + g_pQueuedLoader = (IQueuedLoader *)pFactoryList[i]( QUEUEDLOADER_INTERFACE_VERSION, NULL ); + } + } +} + +void DisconnectTier2Libraries() +{ + + g_pFullFileSystem = 0; + materials = g_pMaterialSystem = 0; + g_pMaterialSystemHardwareConfig = 0; + g_pMaterialSystemDebugTextureInfo = 0; + g_pInputSystem = 0; + g_pNetworkSystem = 0; + colorcorrection = 0; + p4 = 0; + mdllib = 0; + g_pQueuedLoader = 0; +} + + diff --git a/tier2/tier2.vpc b/tier2/tier2.vpc new file mode 100644 index 0000000..53ff36d --- /dev/null +++ b/tier2/tier2.vpc @@ -0,0 +1,58 @@ +//----------------------------------------------------------------------------- +// TIER2.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$macro SRCDIR ".." + +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;$SRCDIR\public\tier2" + } +} + +$Project "tier2" +{ + $Folder "Source Files" + { + $File "beamsegdraw.cpp" + $File "defaultfilesystem.cpp" + $File "dmconnect.cpp" + $file "fileutils.cpp" + $File "keybindings.cpp" + $File "$SRCDIR\public\map_utils.cpp" + $File "$SRCDIR\public\materialsystem\MaterialSystemUtil.cpp" + $File "camerautils.cpp" + $File "meshutils.cpp" + $File "p4helpers.cpp" + $File "renderutils.cpp" + $File "riff.cpp" + $File "soundutils.cpp" + $File "tier2.cpp" + $File "util_init.cpp" + $File "utlstreambuffer.cpp" + $File "vconfig.cpp" + $File "keyvaluesmacros.cpp" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\tier2\beamsegdraw.h" + $File "$SRCDIR\public\tier2\fileutils.h" + $File "$SRCDIR\public\tier2\camerautils.h" + $File "$SRCDIR\public\tier2\meshutils.h" + $File "$SRCDIR\public\tier2\keybindings.h" + $File "$SRCDIR\public\tier2\renderutils.h" + $File "$SRCDIR\public\tier2\riff.h" + $File "$SRCDIR\public\tier2\soundutils.h" + $File "$SRCDIR\public\tier2\tier2.h" + $File "$SRCDIR\public\tier2\utlstreambuffer.h" + $File "$SRCDIR\public\tier2\vconfig.h" + $File "$SRCDIR\public\tier2\keyvaluesmacros.h" + } +} diff --git a/tier2/util_init.cpp b/tier2/util_init.cpp new file mode 100644 index 0000000..1fb1ddd --- /dev/null +++ b/tier2/util_init.cpp @@ -0,0 +1,50 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: perform initialization needed in most command line programs +// +//===================================================================================-=// + +#include +#include +#include +#include +#include "tier0/memalloc.h" +#include "tier0/progressbar.h" +#include "tier1/strtools.h" + + +static void PrintFReportHandler(char const *job_name, int total_units_to_do, int n_units_completed) +{ + static bool work_in_progress=false; + static char LastJobName[1024]; + if ( Q_strncmp( LastJobName, job_name, sizeof( LastJobName ) ) ) + { + if ( work_in_progress ) + printf("..done\n"); + Q_strncpy( LastJobName, job_name, sizeof( LastJobName ) ); + } + if ( (total_units_to_do > 0 ) && (total_units_to_do >= n_units_completed) ) + { + int percent_done=(100*n_units_completed)/total_units_to_do; + printf("\r%s : %d%%",LastJobName, percent_done ); + work_in_progress = true; + } + else + { + printf("%s\n",LastJobName); + work_in_progress = false; + } +} + +void InitCommandLineProgram( int argc, char **argv ) +{ + MathLib_Init( 1,1,1,0,false,true,true,true); + CommandLine()->CreateCmdLine( argc, argv ); + InitDefaultFileSystem(); + InstallProgressReportHandler( PrintFReportHandler ); + + // By default, command line programs should not use the new assert dialog, + // and any asserts should be fatal, unless we are being debugged + if ( !Plat_IsInDebugSession() ) + SpewOutputFunc( DefaultSpewFuncAbortOnAsserts ); +} diff --git a/tier2/utlstreambuffer.cpp b/tier2/utlstreambuffer.cpp new file mode 100644 index 0000000..023a682 --- /dev/null +++ b/tier2/utlstreambuffer.cpp @@ -0,0 +1,386 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +// Serialization/unserialization buffer +//=============================================================================// + + +#include "tier2/utlstreambuffer.h" +#include "tier2/tier2.h" +#include "filesystem.h" + + +//----------------------------------------------------------------------------- +// default stream chunk size +//----------------------------------------------------------------------------- +enum +{ + DEFAULT_STREAM_CHUNK_SIZE = 16 * 1024 +}; + + +//----------------------------------------------------------------------------- +// Constructor, destructor +//----------------------------------------------------------------------------- +CUtlStreamBuffer::CUtlStreamBuffer( ) : BaseClass( DEFAULT_STREAM_CHUNK_SIZE, DEFAULT_STREAM_CHUNK_SIZE, 0 ) +{ + SetUtlBufferOverflowFuncs( &CUtlStreamBuffer::StreamGetOverflow, &CUtlStreamBuffer::StreamPutOverflow ); + m_hFileHandle = FILESYSTEM_INVALID_HANDLE; + m_pFileName = NULL; + m_pPath = NULL; +} + +CUtlStreamBuffer::CUtlStreamBuffer( const char *pFileName, const char *pPath, int nFlags, bool bDelayOpen ) : + BaseClass( DEFAULT_STREAM_CHUNK_SIZE, DEFAULT_STREAM_CHUNK_SIZE, nFlags ) +{ + SetUtlBufferOverflowFuncs( &CUtlStreamBuffer::StreamGetOverflow, &CUtlStreamBuffer::StreamPutOverflow ); + + if ( bDelayOpen ) + { + m_pFileName = V_strdup( pFileName ); + + if ( pPath ) + { + int nPathLen = Q_strlen( pPath ); + m_pPath = new char[ nPathLen + 1 ]; + Q_strcpy( m_pPath, pPath ); + } + else + { + m_pPath = new char[ 1 ]; + m_pPath[0] = 0; + } + + m_hFileHandle = FILESYSTEM_INVALID_HANDLE; + } + else + { + m_pFileName = NULL; + m_pPath = NULL; + m_hFileHandle = OpenFile( pFileName, pPath ); + if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE ) + { + return; + } + } + + if ( IsReadOnly() ) + { + // NOTE: MaxPut may not actually be this exact size for text files; + // it could be slightly less owing to the /r/n -> /n conversion + m_nMaxPut = g_pFullFileSystem->Size( m_hFileHandle ); + + // Read in the first bytes of the file + if ( Size() > 0 ) + { + int nSizeToRead = min( Size(), m_nMaxPut ); + ReadBytesFromFile( nSizeToRead, 0 ); + } + } +} + + +void CUtlStreamBuffer::Close() +{ + if ( !IsReadOnly() ) + { + // Write the final bytes + int nBytesToWrite = TellPut() - m_nOffset; + if ( nBytesToWrite > 0 ) + { + if ( ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE ) && m_pFileName ) + { + m_hFileHandle = OpenFile( m_pFileName, m_pPath ); + if( m_hFileHandle == FILESYSTEM_INVALID_HANDLE ) + { + Error( "CUtlStreamBuffer::Close() Unable to open file %s!\n", m_pFileName ); + } + } + if ( m_hFileHandle != FILESYSTEM_INVALID_HANDLE ) + { + if ( g_pFullFileSystem ) + { + int nBytesWritten = g_pFullFileSystem->Write( Base(), nBytesToWrite, m_hFileHandle ); + if( nBytesWritten != nBytesToWrite ) + { + Error( "CUtlStreamBuffer::Close() Write %s failed %d != %d.\n", m_pFileName, nBytesWritten, nBytesToWrite ); + } + } + } + } + } + + if ( m_hFileHandle != FILESYSTEM_INVALID_HANDLE ) + { + if ( g_pFullFileSystem ) + g_pFullFileSystem->Close( m_hFileHandle ); + m_hFileHandle = FILESYSTEM_INVALID_HANDLE; + } + + if ( m_pFileName ) + { + delete[] m_pFileName; + m_pFileName = NULL; + } + + if ( m_pPath ) + { + delete[] m_pPath; + m_pPath = NULL; + } + + m_Error = 0; +} + +CUtlStreamBuffer::~CUtlStreamBuffer() +{ + Close(); +} + + +//----------------------------------------------------------------------------- +// Open the file. normally done in constructor +//----------------------------------------------------------------------------- +void CUtlStreamBuffer::Open( const char *pFileName, const char *pPath, int nFlags ) +{ + if ( IsOpen() ) + { + Close(); + } + + m_Get = 0; + m_Put = 0; + m_nTab = 0; + m_nOffset = 0; + m_Flags = nFlags; + m_hFileHandle = OpenFile( pFileName, pPath ); + if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE ) + return; + + if ( IsReadOnly() ) + { + // NOTE: MaxPut may not actually be this exact size for text files; + // it could be slightly less owing to the /r/n -> /n conversion + m_nMaxPut = g_pFullFileSystem->Size( m_hFileHandle ); + + // Read in the first bytes of the file + if ( Size() > 0 ) + { + int nSizeToRead = min( Size(), m_nMaxPut ); + ReadBytesFromFile( nSizeToRead, 0 ); + } + } + else + { + if ( m_Memory.NumAllocated() != 0 ) + { + m_nMaxPut = -1; + AddNullTermination(); + } + else + { + m_nMaxPut = 0; + } + } +} + + +//----------------------------------------------------------------------------- +// Is the file open? +//----------------------------------------------------------------------------- +bool CUtlStreamBuffer::IsOpen() const +{ + if ( m_hFileHandle != FILESYSTEM_INVALID_HANDLE ) + return true; + + // Delayed open case + return ( m_pFileName != 0 ); +} + + +//----------------------------------------------------------------------------- +// Grow allocation size to fit requested size +//----------------------------------------------------------------------------- +void CUtlStreamBuffer::GrowAllocatedSize( int nSize ) +{ + int nNewSize = Size(); + if ( nNewSize < nSize + 1 ) + { + while ( nNewSize < nSize + 1 ) + { + nNewSize += DEFAULT_STREAM_CHUNK_SIZE; + } + m_Memory.Grow( nNewSize - Size() ); + } +} + + +//----------------------------------------------------------------------------- +// Load up more of the stream when we overflow +//----------------------------------------------------------------------------- +bool CUtlStreamBuffer::StreamPutOverflow( int nSize ) +{ + if ( !IsValid() || IsReadOnly() ) + return false; + + // Make sure the allocated size is at least as big as the requested size + if ( nSize > 0 ) + { + GrowAllocatedSize( nSize + 2 ); + } + + // Don't write the last byte (for NULL termination logic to work) + int nBytesToWrite = TellPut() - m_nOffset - 1; + if ( ( nBytesToWrite > 0 ) || ( nSize < 0 ) ) + { + if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE ) + { + m_hFileHandle = OpenFile( m_pFileName, m_pPath ); + if( m_hFileHandle == FILESYSTEM_INVALID_HANDLE ) + return false; + } + } + + if ( nBytesToWrite > 0 ) + { + int nBytesWritten = g_pFullFileSystem->Write( Base(), nBytesToWrite, m_hFileHandle ); + if ( nBytesWritten != nBytesToWrite ) + { + m_Error |= FILE_WRITE_ERROR; + return false; + } + + // This is necessary to deal with auto-NULL terminiation + m_Memory[0] = *(unsigned char*)PeekPut( -1 ); + if ( TellPut() < Size() ) + { + m_Memory[1] = *(unsigned char*)PeekPut( ); + } + m_nOffset = TellPut() - 1; + } + + if ( nSize < 0 ) + { + m_nOffset = -nSize-1; + g_pFullFileSystem->Seek( m_hFileHandle, m_nOffset, FILESYSTEM_SEEK_HEAD ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Reads bytes from the file; fixes up maxput if necessary and null terminates +//----------------------------------------------------------------------------- +int CUtlStreamBuffer::ReadBytesFromFile( int nBytesToRead, int nReadOffset ) +{ + if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE ) + { + if ( !m_pFileName ) + { + Warning( "File has not been opened!\n" ); + Assert(0); + return 0; + } + + m_hFileHandle = OpenFile( m_pFileName, m_pPath ); + if ( m_hFileHandle == FILESYSTEM_INVALID_HANDLE ) + { + Error( "Unable to read file %s!\n", m_pFileName ); + return 0; + } + if ( m_nOffset != 0 ) + { + g_pFullFileSystem->Seek( m_hFileHandle, m_nOffset, FILESYSTEM_SEEK_HEAD ); + } + } + + char *pReadPoint = (char*)Base() + nReadOffset; + int nBytesRead = g_pFullFileSystem->Read( pReadPoint, nBytesToRead, m_hFileHandle ); + if ( nBytesRead != nBytesToRead ) + { + // Since max put is a guess at the start, + // we need to shrink it based on the actual # read + if ( m_nMaxPut > TellGet() + nReadOffset + nBytesRead ) + { + m_nMaxPut = TellGet() + nReadOffset + nBytesRead; + } + } + + if ( nReadOffset + nBytesRead < Size() ) + { + // This is necessary to deal with auto-NULL terminiation + pReadPoint[nBytesRead] = 0; + } + + return nBytesRead; +} + + +//----------------------------------------------------------------------------- +// Load up more of the stream when we overflow +//----------------------------------------------------------------------------- +bool CUtlStreamBuffer::StreamGetOverflow( int nSize ) +{ + if ( !IsValid() || !IsReadOnly() ) + return false; + + // Shift the unread bytes down + // NOTE: Can't use the partial overlap path if we're seeking. We'll + // get negative sizes passed in if we're seeking. + int nUnreadBytes; + bool bHasPartialOverlap = ( nSize >= 0 ) && ( TellGet() >= m_nOffset ) && ( TellGet() <= m_nOffset + Size() ); + if ( bHasPartialOverlap ) + { + nUnreadBytes = Size() - ( TellGet() - m_nOffset ); + if ( ( TellGet() != m_nOffset ) && ( nUnreadBytes > 0 ) ) + { + memmove( Base(), (const char*)Base() + TellGet() - m_nOffset, nUnreadBytes ); + } + } + else + { + m_nOffset = TellGet(); + g_pFullFileSystem->Seek( m_hFileHandle, m_nOffset, FILESYSTEM_SEEK_HEAD ); + nUnreadBytes = 0; + } + + // Make sure the allocated size is at least as big as the requested size + if ( nSize > 0 ) + { + GrowAllocatedSize( nSize ); + } + + int nBytesToRead = Size() - nUnreadBytes; + int nBytesRead = ReadBytesFromFile( nBytesToRead, nUnreadBytes ); + if ( nBytesRead == 0 ) + return false; + + m_nOffset = TellGet(); + return ( nBytesRead + nUnreadBytes >= nSize ); +} + + +//----------------------------------------------------------------------------- +// open file unless already failed to open +//----------------------------------------------------------------------------- +FileHandle_t CUtlStreamBuffer::OpenFile( const char *pFileName, const char *pPath ) +{ + if ( m_Error & FILE_OPEN_ERROR ) + return FILESYSTEM_INVALID_HANDLE; + + char openflags[ 3 ] = "xx"; + openflags[ 0 ] = IsReadOnly() ? 'r' : 'w'; + openflags[ 1 ] = IsText() && !ContainsCRLF() ? 't' : 'b'; + + FileHandle_t fh = g_pFullFileSystem->Open( pFileName, openflags, pPath ); + if( fh == FILESYSTEM_INVALID_HANDLE ) + { + m_Error |= FILE_OPEN_ERROR; + } + + return fh; +} diff --git a/tier2/vconfig.cpp b/tier2/vconfig.cpp new file mode 100644 index 0000000..ff90afc --- /dev/null +++ b/tier2/vconfig.cpp @@ -0,0 +1,149 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Utilities for setting vproject settings +// +//===========================================================================// + +#ifdef _WIN32 +#if !defined( _X360 ) +#include +#endif +#include +#include // _chmod +#include +#endif +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif +#include "vconfig.h" + + +#ifdef _WIN32 +//----------------------------------------------------------------------------- +// Purpose: Returns the string value of a registry key +// Input : *pName - name of the subKey to read +// *pReturn - string buffer to receive read string +// size - size of specified buffer +//----------------------------------------------------------------------------- +bool GetVConfigRegistrySetting( const char *pName, char *pReturn, int size ) +{ + // Open the key + HKEY hregkey; + // Changed to HKEY_CURRENT_USER from HKEY_LOCAL_MACHINE + if ( RegOpenKeyEx( HKEY_CURRENT_USER, VPROJECT_REG_KEY, 0, KEY_QUERY_VALUE, &hregkey ) != ERROR_SUCCESS ) + return false; + + // Get the value + DWORD dwSize = size; + if ( RegQueryValueEx( hregkey, pName, NULL, NULL,(LPBYTE) pReturn, &dwSize ) != ERROR_SUCCESS ) + return false; + + // Close the key + RegCloseKey( hregkey ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Sends a global system message to alert programs to a changed environment variable +//----------------------------------------------------------------------------- +void NotifyVConfigRegistrySettingChanged( void ) +{ + DWORD_PTR dwReturnValue = 0; + + // Propagate changes so that environment variables takes immediate effect! + SendMessageTimeout( HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM) "Environment", SMTO_ABORTIFHUNG, 5000, &dwReturnValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Set the registry entry to a string value, under the given subKey +// Input : *pName - name of the subKey to set +// *pValue - string value +//----------------------------------------------------------------------------- +void SetVConfigRegistrySetting( const char *pName, const char *pValue, bool bNotify ) +{ + HKEY hregkey; + + // Changed to HKEY_CURRENT_USER from HKEY_LOCAL_MACHINE + // Open the key + if ( RegCreateKeyEx( + HKEY_CURRENT_USER, // base key + VPROJECT_REG_KEY, // subkey + 0, // reserved + 0, // lpClass + 0, // options + (REGSAM)KEY_ALL_ACCESS, // access desired + NULL, // security attributes + &hregkey, // result + NULL // tells if it created the key or not (which we don't care) + ) != ERROR_SUCCESS ) + { + return; + } + + // Set the value to the string passed in + int nType = strchr( pValue, '%' ) ? REG_EXPAND_SZ : REG_SZ; + RegSetValueEx( hregkey, pName, 0, nType, (const unsigned char *)pValue, (int) strlen(pValue) ); + + // Notify other programs + if ( bNotify ) + { + NotifyVConfigRegistrySettingChanged(); + } + + // Close the key + RegCloseKey( hregkey ); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes the obsolete user keyvalue +// Input : *pName - name of the subKey to set +// *pValue - string value +//----------------------------------------------------------------------------- +bool RemoveObsoleteVConfigRegistrySetting( const char *pValueName, char *pOldValue, int size ) +{ + // Open the key + HKEY hregkey; + if ( RegOpenKeyEx( HKEY_CURRENT_USER, "Environment", 0, (REGSAM)KEY_ALL_ACCESS, &hregkey ) != ERROR_SUCCESS ) + return false; + + // Return the old state if they've requested it + if ( pOldValue != NULL ) + { + DWORD dwSize = size; + + // Get the value + if ( RegQueryValueEx( hregkey, pValueName, NULL, NULL,(LPBYTE) pOldValue, &dwSize ) != ERROR_SUCCESS ) + return false; + } + + // Remove the value + if ( RegDeleteValue( hregkey, pValueName ) != ERROR_SUCCESS ) + return false; + + // Close the key + RegCloseKey( hregkey ); + + // Notify other programs + NotifyVConfigRegistrySettingChanged(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Take a user-defined environment variable and swap it out for the internally used one +//----------------------------------------------------------------------------- + +bool ConvertObsoleteVConfigRegistrySetting( const char *pValueName ) +{ + char szValue[MAX_PATH]; + if ( RemoveObsoleteVConfigRegistrySetting( pValueName, szValue, sizeof( szValue ) ) ) + { + // Set it up the correct way + SetVConfigRegistrySetting( pValueName, szValue ); + return true; + } + + return false; +} +#endif diff --git a/tier3/choreoutils.cpp b/tier3/choreoutils.cpp new file mode 100644 index 0000000..613103e --- /dev/null +++ b/tier3/choreoutils.cpp @@ -0,0 +1,347 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Helper methods + classes for file access +// +//===========================================================================// + +#include "tier3/choreoutils.h" +#include "tier3/tier3.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "studio.h" +#include "../game/shared/choreoscene.h" +#include "../game/shared/choreoevent.h" +#include "tier1/KeyValues.h" +#include "bone_setup.h" +#include "soundchars.h" + + +//----------------------------------------------------------------------------- +// Find sequence by name +//----------------------------------------------------------------------------- +static int LookupSequence( CStudioHdr *pStudioHdr, const char *pSequenceName ) +{ + for ( int i = 0; i < pStudioHdr->GetNumSeq(); i++ ) + { + if ( !Q_stricmp( pSequenceName, pStudioHdr->pSeqdesc( i ).pszLabel() ) ) + return i; + } + return -1; +} + + +//----------------------------------------------------------------------------- +// Returns sequence flags +//----------------------------------------------------------------------------- +static int GetSequenceFlags( CStudioHdr *pStudioHdr, int nSequence ) +{ + if ( !pStudioHdr || nSequence < 0 || nSequence >= pStudioHdr->GetNumSeq() ) + return 0; + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( nSequence ); + return seqdesc.flags; +} + + +//----------------------------------------------------------------------------- +// Does a sequence loop? +//----------------------------------------------------------------------------- +static bool DoesSequenceLoop( CStudioHdr *pStudioHdr, int nSequence ) +{ + int nFlags = GetSequenceFlags( pStudioHdr, nSequence ); + bool bLooping = ( nFlags & STUDIO_LOOPING ) ? true : false; + return bLooping; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool AutoAddGestureKeys( CChoreoEvent *e, CStudioHdr *pStudioHdr, float *pPoseParameters, bool bCheckOnly ) +{ + int iSequence = LookupSequence( pStudioHdr, e->GetParameters() ); + if ( iSequence < 0 ) + return false; + + KeyValues *pSeqKeyValues = new KeyValues( "" ); + if ( !pSeqKeyValues->LoadFromBuffer( pStudioHdr->pszName(), Studio_GetKeyValueText( pStudioHdr, iSequence ) ) ) + { + pSeqKeyValues->deleteThis(); + return false; + } + + // Do we have a build point section? + KeyValues *pKVAllFaceposer = pSeqKeyValues->FindKey("faceposer"); + if ( !pKVAllFaceposer ) + { + pSeqKeyValues->deleteThis(); + return false; + } + + int nMaxFrame = Studio_MaxFrame( pStudioHdr, iSequence, pPoseParameters ) - 1; + + // Start grabbing the sounds and slotting them in + KeyValues *pkvFaceposer; + char szStartLoop[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "loop" }; + char szEndLoop[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "end" }; + char szEntry[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "apex" }; + char szExit[CEventAbsoluteTag::MAX_EVENTTAG_LENGTH] = { "end" }; + + for ( pkvFaceposer = pKVAllFaceposer->GetFirstSubKey(); pkvFaceposer; pkvFaceposer = pkvFaceposer->GetNextKey() ) + { + if ( !Q_stricmp( pkvFaceposer->GetName(), "startloop" ) ) + { + Q_strncpy( szStartLoop, pkvFaceposer->GetString(), sizeof(szStartLoop) ); + continue; + } + + if ( !Q_stricmp( pkvFaceposer->GetName(), "endloop" ) ) + { + Q_strncpy( szEndLoop, pkvFaceposer->GetString(), sizeof(szEndLoop) ); + continue; + } + + if ( !Q_stricmp( pkvFaceposer->GetName(), "entrytag" ) ) + { + Q_strncpy( szEntry, pkvFaceposer->GetString(), sizeof(szEntry) ); + continue; + } + + if ( !Q_stricmp( pkvFaceposer->GetName(), "exittag" ) ) + { + Q_strncpy( szExit, pkvFaceposer->GetString(), sizeof(szExit) ); + continue; + } + + if ( !Q_stricmp( pkvFaceposer->GetName(), "tags" ) ) + { + if ( nMaxFrame <= 0 ) + continue; + + KeyValues *pkvTags; + for ( pkvTags = pkvFaceposer->GetFirstSubKey(); pkvTags; pkvTags = pkvTags->GetNextKey() ) + { + float flPercentage = (float)pkvTags->GetInt() / nMaxFrame; + + CEventAbsoluteTag *ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, pkvTags->GetName() ); + if (ptag) + { + // reposition tag + ptag->SetPercentage( flPercentage ); + } + else + { + e->AddAbsoluteTag( CChoreoEvent::ORIGINAL, pkvTags->GetName(), flPercentage ); + e->AddAbsoluteTag( CChoreoEvent::PLAYBACK, pkvTags->GetName(), flPercentage ); + } + // lock the original tags so they can't be edited + ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, pkvTags->GetName() ); + Assert( ptag ); + ptag->SetLocked( true ); + } + e->VerifyTagOrder(); + e->PreventTagOverlap(); + continue; + } + } + + // FIXME: lookup linear tags in sequence data + { + CEventAbsoluteTag *ptag; + ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szStartLoop ); + if (ptag) + { + ptag->SetLinear( true ); + } + ptag = e->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szStartLoop ); + if (ptag) + { + ptag->SetLinear( true ); + } + ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szEndLoop ); + if (ptag) + { + ptag->SetLinear( true ); + } + ptag = e->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szEndLoop ); + if (ptag) + { + ptag->SetLinear( true ); + } + + ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szEntry ); + if (ptag) + { + ptag->SetEntry( true ); + } + ptag = e->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szEntry ); + if (ptag) + { + ptag->SetEntry( true ); + } + ptag = e->FindAbsoluteTag( CChoreoEvent::ORIGINAL, szExit ); + if (ptag) + { + ptag->SetExit( true ); + } + ptag = e->FindAbsoluteTag( CChoreoEvent::PLAYBACK, szExit ); + if (ptag) + { + ptag->SetExit( true ); + } + } + + pSeqKeyValues->deleteThis(); + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool UpdateGestureLength( CChoreoEvent *e, CStudioHdr *pStudioHdr, float *pPoseParameters, bool bCheckOnly ) +{ + Assert( e ); + if ( !e ) + return false; + + if ( e->GetType() != CChoreoEvent::GESTURE ) + return false; + + int iSequence = LookupSequence( pStudioHdr, e->GetParameters() ); + if ( iSequence < 0 ) + return false; + + bool bChanged = false; + float flSeqDuration = Studio_Duration( pStudioHdr, iSequence, pPoseParameters ); + float flCurDuration; + e->GetGestureSequenceDuration( flCurDuration ); + if ( flSeqDuration != 0.0f && flSeqDuration != flCurDuration ) + { + bChanged = true; + if ( !bCheckOnly ) + { + e->SetGestureSequenceDuration( flSeqDuration ); + } + } + + return bChanged; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool UpdateSequenceLength( CChoreoEvent *e, CStudioHdr *pStudioHdr, float *pPoseParameters, bool bCheckOnly, bool bVerbose ) +{ + Assert( e ); + if ( !e ) + return false; + + if ( e->GetType() != CChoreoEvent::SEQUENCE ) + { + if ( bVerbose ) + { + ConMsg( "UpdateSequenceLength: called on non-SEQUENCE event %s\n", e->GetName() ); + } + return false; + } + + int iSequence = LookupSequence( pStudioHdr, e->GetParameters() ); + if ( iSequence < 0 ) + return false; + + bool bChanged = false; + bool bLooping = DoesSequenceLoop( pStudioHdr, iSequence ); + float flSeqDuration = Studio_Duration( pStudioHdr, iSequence, pPoseParameters ); + + if ( bLooping ) + { + if ( e->IsFixedLength() ) + { + if ( bCheckOnly ) + return true; + + if ( bVerbose ) + { + ConMsg( "UpdateSequenceLength: %s is looping, removing fixed length flag\n", e->GetName() ); + } + bChanged = true; + } + e->SetFixedLength( false ); + + if ( !e->HasEndTime() ) + { + if ( bCheckOnly ) + return true; + + if ( bVerbose ) + { + ConMsg( "CheckSequenceLength: %s is looping, setting default end time\n", e->GetName() ); + } + e->SetEndTime( e->GetStartTime() + flSeqDuration ); + bChanged = true; + } + + return bChanged; + } + + if ( !e->IsFixedLength() ) + { + if ( bCheckOnly ) + return true; + + if ( bVerbose ) + { + ConMsg( "CheckSequenceLength: %s is fixed length, removing looping flag\n", e->GetName() ); + } + bChanged = true; + } + e->SetFixedLength( true ); + + if ( e->HasEndTime() ) + { + float dt = e->GetDuration(); + if ( fabs( dt - flSeqDuration ) > 0.01f ) + { + if ( bCheckOnly ) + return true; + if ( bVerbose ) + { + ConMsg( "CheckSequenceLength: %s has wrong duration, changing length from %f to %f seconds\n", + e->GetName(), dt, flSeqDuration ); + } + bChanged = true; + } + } + else + { + if ( bCheckOnly ) + return true; + if ( bVerbose ) + { + ConMsg( "CheckSequenceLength: %s has wrong duration, changing length to %f seconds\n", + e->GetName(), flSeqDuration ); + } + bChanged = true; + } + + if ( !bCheckOnly ) + { + e->SetEndTime( e->GetStartTime() + flSeqDuration ); + } + + return bChanged; +} + + +//----------------------------------------------------------------------------- +// Finds sound files associated with events +//----------------------------------------------------------------------------- +const char *GetSoundForEvent( CChoreoEvent *pEvent, CStudioHdr *pStudioHdr ) +{ + const char *pSoundName = pEvent->GetParameters(); + if ( Q_stristr( pSoundName, ".wav" ) ) + return PSkipSoundChars( pSoundName ); + + const char *pFileName = g_pSoundEmitterSystem->GetWavFileForSound( pSoundName, ( pStudioHdr && pStudioHdr->IsValid() ) ? pStudioHdr->pszName() : NULL ); + return PSkipSoundChars( pFileName ); +} diff --git a/tier3/mdlutils.cpp b/tier3/mdlutils.cpp new file mode 100644 index 0000000..afe8729 --- /dev/null +++ b/tier3/mdlutils.cpp @@ -0,0 +1,459 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Utility methods for mdl files +// +//===========================================================================// + +#include "tier3/mdlutils.h" +#include "tier0/dbg.h" +#include "tier1/callqueue.h" +#include "tier3/tier3.h" +#include "studio.h" +#include "istudiorender.h" +#include "bone_setup.h" + + +//----------------------------------------------------------------------------- +// Returns the bounding box for the model +//----------------------------------------------------------------------------- +void GetMDLBoundingBox( Vector *pMins, Vector *pMaxs, MDLHandle_t h, int nSequence ) +{ + if ( h == MDLHANDLE_INVALID || !g_pMDLCache ) + { + pMins->Init(); + pMaxs->Init(); + return; + } + + pMins->Init( FLT_MAX, FLT_MAX ); + pMaxs->Init( -FLT_MAX, -FLT_MAX ); + + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( h ); + if ( !VectorCompare( vec3_origin, pStudioHdr->view_bbmin ) || !VectorCompare( vec3_origin, pStudioHdr->view_bbmax )) + { + // look for view clip + *pMins = pStudioHdr->view_bbmin; + *pMaxs = pStudioHdr->view_bbmax; + } + else if ( !VectorCompare( vec3_origin, pStudioHdr->hull_min ) || !VectorCompare( vec3_origin, pStudioHdr->hull_max )) + { + // look for hull + *pMins = pStudioHdr->hull_min; + *pMaxs = pStudioHdr->hull_max; + } + + // Else use the sequence box + mstudioseqdesc_t &seqdesc = pStudioHdr->pSeqdesc( nSequence ); + VectorMin( seqdesc.bbmin, *pMins, *pMins ); + VectorMax( seqdesc.bbmax, *pMaxs, *pMaxs ); +} + + +//----------------------------------------------------------------------------- +// Returns the radius of the model as measured from the origin +//----------------------------------------------------------------------------- +float GetMDLRadius( MDLHandle_t h, int nSequence ) +{ + Vector vecMins, vecMaxs; + GetMDLBoundingBox( &vecMins, &vecMaxs, h, nSequence ); + float flRadius = vecMaxs.Length(); + float flRadius2 = vecMins.Length(); + if ( flRadius2 > flRadius ) + { + flRadius = flRadius2; + } + return flRadius; +} + + +//----------------------------------------------------------------------------- +// Returns a more accurate bounding sphere +//----------------------------------------------------------------------------- +void GetMDLBoundingSphere( Vector *pVecCenter, float *pRadius, MDLHandle_t h, int nSequence ) +{ + Vector vecMins, vecMaxs; + GetMDLBoundingBox( &vecMins, &vecMaxs, h, nSequence ); + VectorAdd( vecMins, vecMaxs, *pVecCenter ); + *pVecCenter *= 0.5f; + *pRadius = vecMaxs.DistTo( *pVecCenter ); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CMDL::CMDL() +{ + m_MDLHandle = MDLHANDLE_INVALID; + m_Color.SetColor( 255, 255, 255, 255 ); + m_nSkin = 0; + m_nBody = 0; + m_nSequence = 0; + m_nLOD = 0; + m_flPlaybackRate = 30.0f; + m_flTime = 0.0f; + m_vecViewTarget.Init( 0, 0, 0 ); + m_bWorldSpaceViewTarget = false; + memset( m_pFlexControls, 0, sizeof(m_pFlexControls) ); + m_pProxyData = NULL; +} + +CMDL::~CMDL() +{ + UnreferenceMDL(); +} + +void CMDL::SetMDL( MDLHandle_t h ) +{ + UnreferenceMDL(); + m_MDLHandle = h; + if ( m_MDLHandle != MDLHANDLE_INVALID ) + { + g_pMDLCache->AddRef( m_MDLHandle ); + + studiohdr_t *pHdr = g_pMDLCache->LockStudioHdr( m_MDLHandle ); + + if ( pHdr ) + { + for ( LocalFlexController_t i = LocalFlexController_t(0); i < pHdr->numflexcontrollers; ++i ) + { + if ( pHdr->pFlexcontroller( i )->localToGlobal == -1 ) + { + pHdr->pFlexcontroller( i )->localToGlobal = i; + } + } + } + } +} + +MDLHandle_t CMDL::GetMDL() const +{ + return m_MDLHandle; +} + + +//----------------------------------------------------------------------------- +// Release the MDL handle +//----------------------------------------------------------------------------- +void CMDL::UnreferenceMDL() +{ + if ( !g_pMDLCache ) + return; + + if ( m_MDLHandle != MDLHANDLE_INVALID ) + { + // XXX need to figure out where it is safe to flush the queue during map change to not crash +#if 0 + if ( ICallQueue *pCallQueue = materials->GetRenderContext()->GetCallQueue() ) + { + // Parallel rendering: don't unlock model data until end of rendering + pCallQueue->QueueCall( g_pMDLCache, &IMDLCache::UnlockStudioHdr, m_MDLHandle ); + pCallQueue->QueueCall( g_pMDLCache, &IMDLCache::Release, m_MDLHandle ); + } + else +#endif + { + // Immediate-mode rendering, can unlock immediately + g_pMDLCache->UnlockStudioHdr( m_MDLHandle ); + g_pMDLCache->Release( m_MDLHandle ); + } + m_MDLHandle = MDLHANDLE_INVALID; + } +} + + +//----------------------------------------------------------------------------- +// Gets the studiohdr +//----------------------------------------------------------------------------- +studiohdr_t *CMDL::GetStudioHdr() +{ + if ( !g_pMDLCache ) + return NULL; + return g_pMDLCache->GetStudioHdr( m_MDLHandle ); +} + + +//----------------------------------------------------------------------------- +// Draws the mesh +//----------------------------------------------------------------------------- +void CMDL::Draw( const matrix3x4_t& rootToWorld, const matrix3x4_t *pBoneToWorld ) +{ + if ( !g_pMaterialSystem || !g_pMDLCache || !g_pStudioRender ) + return; + + if ( m_MDLHandle == MDLHANDLE_INVALID ) + return; + + // Color + alpha modulation + Vector white( m_Color.r() / 255.0f, m_Color.g() / 255.0f, m_Color.b() / 255.0f ); + g_pStudioRender->SetColorModulation( white.Base() ); + g_pStudioRender->SetAlphaModulation( m_Color.a() / 255.0f ); + + DrawModelInfo_t info; + info.m_pStudioHdr = g_pMDLCache->GetStudioHdr( m_MDLHandle ); + info.m_pHardwareData = g_pMDLCache->GetHardwareData( m_MDLHandle ); + info.m_Decals = STUDIORENDER_DECAL_INVALID; + info.m_Skin = m_nSkin; + info.m_Body = m_nBody; + info.m_HitboxSet = 0; + info.m_pClientEntity = m_pProxyData; + info.m_pColorMeshes = NULL; + info.m_bStaticLighting = false; + info.m_Lod = m_nLOD; + + Vector vecWorldViewTarget; + if ( m_bWorldSpaceViewTarget ) + { + vecWorldViewTarget = m_vecViewTarget; + } + else + { + VectorTransform( m_vecViewTarget, rootToWorld, vecWorldViewTarget ); + } + g_pStudioRender->SetEyeViewTarget( info.m_pStudioHdr, info.m_Body, vecWorldViewTarget ); + + // FIXME: Why is this necessary!?!?!? + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + + // Set default flex values + float *pFlexWeights = NULL; + const int nFlexDescCount = info.m_pStudioHdr->numflexdesc; + if ( nFlexDescCount ) + { + CStudioHdr cStudioHdr( info.m_pStudioHdr, g_pMDLCache ); + + g_pStudioRender->LockFlexWeights( info.m_pStudioHdr->numflexdesc, &pFlexWeights ); + cStudioHdr.RunFlexRules( m_pFlexControls, pFlexWeights ); + g_pStudioRender->UnlockFlexWeights(); + } + + Vector vecModelOrigin; + MatrixGetColumn( rootToWorld, 3, vecModelOrigin ); + g_pStudioRender->DrawModel( NULL, info, const_cast( pBoneToWorld ), + pFlexWeights, NULL, vecModelOrigin, STUDIORENDER_DRAW_ENTIRE_MODEL ); +} + +void CMDL::Draw( const matrix3x4_t &rootToWorld ) +{ + if ( !g_pMaterialSystem || !g_pMDLCache || !g_pStudioRender ) + return; + + if ( m_MDLHandle == MDLHANDLE_INVALID ) + return; + + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( m_MDLHandle ); + + matrix3x4_t *pBoneToWorld = g_pStudioRender->LockBoneMatrices( pStudioHdr->numbones ); + SetUpBones( rootToWorld, pStudioHdr->numbones, pBoneToWorld ); + g_pStudioRender->UnlockBoneMatrices(); + + Draw( rootToWorld, pBoneToWorld ); +} + + +void CMDL::SetUpBones( const matrix3x4_t& rootToWorld, int nMaxBoneCount, matrix3x4_t *pBoneToWorld, const float *pPoseParameters, MDLSquenceLayer_t *pSequenceLayers, int nNumSequenceLayers ) +{ + CStudioHdr studioHdr( g_pMDLCache->GetStudioHdr( m_MDLHandle ), g_pMDLCache ); + + float pPoseParameter[MAXSTUDIOPOSEPARAM]; + if ( pPoseParameters ) + { + V_memcpy( pPoseParameter, pPoseParameters, sizeof(pPoseParameter) ); + } + else + { + // Default to middle of the pose parameter range + int nPoseCount = studioHdr.GetNumPoseParameters(); + for ( int i = 0; i < MAXSTUDIOPOSEPARAM; ++i ) + { + pPoseParameter[i] = 0.5f; + if ( i < nPoseCount ) + { + const mstudioposeparamdesc_t &Pose = studioHdr.pPoseParameter( i ); + + // Want to try for a zero state. If one doesn't exist set it to .5 by default. + if ( Pose.start < 0.0f && Pose.end > 0.0f ) + { + float flPoseDelta = Pose.end - Pose.start; + pPoseParameter[i] = -Pose.start / flPoseDelta; + } + } + } + } + + int nFrameCount = Studio_MaxFrame( &studioHdr, m_nSequence, pPoseParameter ); + if ( nFrameCount == 0 ) + { + nFrameCount = 1; + } + float flCycle = ( m_flTime * m_flPlaybackRate ) / nFrameCount; + + // FIXME: We're always wrapping; may want to determing if we should clamp + flCycle -= (int)(flCycle); + + Vector pos[MAXSTUDIOBONES]; + Quaternion q[MAXSTUDIOBONES]; + + IBoneSetup boneSetup( &studioHdr, BONE_USED_BY_ANYTHING_AT_LOD( m_nLOD ), pPoseParameter, NULL ); + boneSetup.InitPose( pos, q ); + boneSetup.AccumulatePose( pos, q, m_nSequence, flCycle, 1.0f, m_flTime, NULL ); + + // Accumulate the additional layers if specified. + if ( pSequenceLayers ) + { + int nNumSeq = studioHdr.GetNumSeq(); + for ( int i = 0; i < nNumSequenceLayers; ++i ) + { + int nSeqIndex = pSequenceLayers[ i ].m_nSequenceIndex; + if ( ( nSeqIndex >= 0 ) && ( nSeqIndex < nNumSeq ) ) + { + float flWeight = pSequenceLayers[ i ].m_flWeight; + + float flLayerCycle; + int nLayerFrameCount = MAX( 1, Studio_MaxFrame( &studioHdr, nSeqIndex, pPoseParameter ) ); + + if ( pSequenceLayers[i].m_bNoLoop ) + { + if ( pSequenceLayers[i].m_flCycleBeganAt == 0 ) + { + pSequenceLayers[i].m_flCycleBeganAt = m_flTime; + } + + float flElapsedTime = m_flTime - pSequenceLayers[i].m_flCycleBeganAt; + flLayerCycle = ( flElapsedTime * m_flPlaybackRate ) / nLayerFrameCount; + + // Should we keep playing layers that have ended? + //if ( flLayerCycle >= 1.0 ) + //continue; + } + else + { + flLayerCycle = ( m_flTime * m_flPlaybackRate ) / nLayerFrameCount; + + // FIXME: We're always wrapping; may want to determing if we should clamp + flLayerCycle -= (int)(flLayerCycle); + } + + boneSetup.AccumulatePose( pos, q, nSeqIndex, flLayerCycle, flWeight, m_flTime, NULL ); + } + } + } + + // FIXME: Try enabling this? + // CalcAutoplaySequences( pStudioHdr, NULL, pos, q, pPoseParameter, BONE_USED_BY_VERTEX_AT_LOD( m_nLOD ), flTime ); + + matrix3x4_t temp; + + if ( nMaxBoneCount > studioHdr.numbones() ) + { + nMaxBoneCount = studioHdr.numbones(); + } + + for ( int i = 0; i < nMaxBoneCount; i++ ) + { + // If it's not being used, fill with NAN for errors +#ifdef _DEBUG + if ( !(studioHdr.pBone( i )->flags & BONE_USED_BY_ANYTHING_AT_LOD( m_nLOD ) ) ) + { + int j, k; + for (j = 0; j < 3; j++) + { + for (k = 0; k < 4; k++) + { + pBoneToWorld[i][j][k] = VEC_T_NAN; + } + } + continue; + } +#endif + + matrix3x4_t boneMatrix; + QuaternionMatrix( q[i], boneMatrix ); + MatrixSetColumn( pos[i], 3, boneMatrix ); + + if ( studioHdr.pBone(i)->parent == -1 ) + { + ConcatTransforms( rootToWorld, boneMatrix, pBoneToWorld[i] ); + } + else + { + ConcatTransforms( pBoneToWorld[ studioHdr.pBone(i)->parent ], boneMatrix, pBoneToWorld[i] ); + } + } + Studio_RunBoneFlexDrivers( m_pFlexControls, &studioHdr, pos, pBoneToWorld, rootToWorld ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMDL::SetupBonesWithBoneMerge( const CStudioHdr *pMergeHdr, matrix3x4_t *pMergeBoneToWorld, + const CStudioHdr *pFollow, const matrix3x4_t *pFollowBoneToWorld, + const matrix3x4_t &matModelToWorld ) +{ + // Default to middle of the pose parameter range + int nPoseCount = pMergeHdr->GetNumPoseParameters(); + float pPoseParameter[MAXSTUDIOPOSEPARAM]; + for ( int i = 0; i < MAXSTUDIOPOSEPARAM; ++i ) + { + pPoseParameter[i] = 0.5f; + if ( i < nPoseCount ) + { + const mstudioposeparamdesc_t &Pose = ((CStudioHdr *)pMergeHdr)->pPoseParameter( i ); + + // Want to try for a zero state. If one doesn't exist set it to .5 by default. + if ( Pose.start < 0.0f && Pose.end > 0.0f ) + { + float flPoseDelta = Pose.end - Pose.start; + pPoseParameter[i] = -Pose.start / flPoseDelta; + } + } + } + + int nFrameCount = Studio_MaxFrame( pMergeHdr, m_nSequence, pPoseParameter ); + if ( nFrameCount == 0 ) + { + nFrameCount = 1; + } + float flCycle = ( m_flTime * m_flPlaybackRate ) / nFrameCount; + + // FIXME: We're always wrapping; may want to determing if we should clamp + flCycle -= (int)(flCycle); + + Vector pos[MAXSTUDIOBONES]; + Quaternion q[MAXSTUDIOBONES]; + + IBoneSetup boneSetup( pMergeHdr, BONE_USED_BY_ANYTHING_AT_LOD( m_nLOD ), pPoseParameter ); + boneSetup.InitPose( pos, q ); + boneSetup.AccumulatePose( pos, q, m_nSequence, flCycle, 1.0f, m_flTime, NULL ); + + // Get the merge bone list. + mstudiobone_t *pMergeBones = pMergeHdr->pBone( 0 ); + for ( int iMergeBone = 0; iMergeBone < pMergeHdr->numbones(); ++iMergeBone ) + { + // Now find the bone in the parent entity. + bool bMerged = false; + int iParentBoneIndex = Studio_BoneIndexByName( pFollow, pMergeBones[iMergeBone].pszName() ); + if ( iParentBoneIndex >= 0 ) + { + MatrixCopy( pFollowBoneToWorld[iParentBoneIndex], pMergeBoneToWorld[iMergeBone] ); + bMerged = true; + } + + if ( !bMerged ) + { + // If we get down here, then the bone wasn't merged. + matrix3x4_t matBone; + QuaternionMatrix( q[iMergeBone], pos[iMergeBone], matBone ); + + if ( pMergeBones[iMergeBone].parent == -1 ) + { + ConcatTransforms( matModelToWorld, matBone, pMergeBoneToWorld[iMergeBone] ); + } + else + { + ConcatTransforms( pMergeBoneToWorld[pMergeBones[iMergeBone].parent], matBone, pMergeBoneToWorld[iMergeBone] ); + } + } + } +} + diff --git a/tier3/scenetokenprocessor.cpp b/tier3/scenetokenprocessor.cpp new file mode 100644 index 0000000..b1730a5 --- /dev/null +++ b/tier3/scenetokenprocessor.cpp @@ -0,0 +1,201 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "../game/shared/choreoscene.h" +#include "../game/shared/choreoactor.h" +#include "../game/shared/choreochannel.h" +#include "../game/shared/choreoevent.h" +#include "../game/shared/iscenetokenprocessor.h" +#include "characterset.h" + + +//----------------------------------------------------------------------------- +// Purpose: Helper for parsing scene data file +//----------------------------------------------------------------------------- +class CSceneTokenProcessor : public ISceneTokenProcessor +{ +public: + CSceneTokenProcessor(); + + const char *CurrentToken( void ); + bool GetToken( bool crossline ); + bool TokenAvailable( void ); + void Error( const char *fmt, ... ); + void SetBuffer( char *buffer ); +private: + + const char *ParseNextToken (const char *data); + + const char *m_pBuffer; + char m_szToken[ 1024 ]; + + characterset_t m_BreakSetIncludingColons; +}; + +CSceneTokenProcessor::CSceneTokenProcessor() +{ + CharacterSetBuild( &m_BreakSetIncludingColons, "{}()':" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CSceneTokenProcessor::CurrentToken( void ) +{ + return m_szToken; +} + +const char *CSceneTokenProcessor::ParseNextToken (const char *data) +{ + unsigned char c; + int len; + characterset_t *breaks; + + breaks = &m_BreakSetIncludingColons; + + len = 0; + m_szToken[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + m_szToken[len] = 0; + return data; + } + m_szToken[len] = c; + len++; + } + } + +// parse single characters + if ( IN_CHARACTERSET( *breaks, c ) ) + { + m_szToken[len] = c; + len++; + m_szToken[len] = 0; + return data+1; + } + +// parse a regular word + do + { + m_szToken[len] = c; + data++; + len++; + c = *data; + if ( IN_CHARACTERSET( *breaks, c ) ) + break; + } while (c>32); + + m_szToken[len] = 0; + return data; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : crossline - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CSceneTokenProcessor::GetToken( bool crossline ) +{ + // NOTE: crossline is ignored here, may need to implement if needed + m_pBuffer = ParseNextToken( m_pBuffer ); + if ( m_szToken[0] ) + return true; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CSceneTokenProcessor::TokenAvailable( void ) +{ + const char *search_p = m_pBuffer; + + while ( *search_p <= 32) + { + if (*search_p == '\n') + return false; + search_p++; + if ( !*search_p ) + return false; + + } + + if (*search_p == ';' || *search_p == '#' || // semicolon and # is comment field + (*search_p == '/' && *((search_p)+1) == '/')) // also make // a comment field + return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *fmt - +// ... - +//----------------------------------------------------------------------------- +void CSceneTokenProcessor::Error( const char *fmt, ... ) +{ + char string[ 2048 ]; + va_list argptr; + va_start( argptr, fmt ); + Q_vsnprintf( string, sizeof(string), fmt, argptr ); + va_end( argptr ); + + Warning( "%s", string ); + Assert(0); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *buffer - +//----------------------------------------------------------------------------- +void CSceneTokenProcessor::SetBuffer( char *buffer ) +{ + m_pBuffer = buffer; +} + +CSceneTokenProcessor g_TokenProcessor; + +ISceneTokenProcessor *GetTokenProcessor() +{ + return &g_TokenProcessor; +} + +void SetTokenProcessorBuffer( const char *buf ) +{ + g_TokenProcessor.SetBuffer( (char *)buf ); +} + diff --git a/tier3/studiohdrstub.cpp b/tier3/studiohdrstub.cpp new file mode 100644 index 0000000..44d05ad --- /dev/null +++ b/tier3/studiohdrstub.cpp @@ -0,0 +1,47 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= +//#include "studio.h" +#include "studio.h" +#include "datacache/imdlcache.h" +#include "datamodel/dmelementfactoryhelper.h" +#include "istudiorender.h" +#include "bone_setup.h" +#include "tier3/tier3.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// FIXME: This trashy glue code is really not acceptable. Figure out a way of making it unnecessary. +//----------------------------------------------------------------------------- +const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *pModelName ) const +{ + MDLHandle_t handle = g_pMDLCache->FindMDL( pModelName ); + *cache = (void*)(uintp)handle; + return g_pMDLCache->GetStudioHdr( handle ); +} + +virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const +{ + return g_pMDLCache->GetVirtualModel( (MDLHandle_t)((int)virtualModel&0xffff) ); +} + +byte *studiohdr_t::GetAnimBlock( int i ) const +{ + return g_pMDLCache->GetAnimBlock( (MDLHandle_t)((int)virtualModel&0xffff), i ); +} + +int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const +{ + return g_pMDLCache->GetAutoplayList( (MDLHandle_t)((int)virtualModel&0xffff), pOut ); +} + +const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const +{ + return g_pMDLCache->GetStudioHdr( (MDLHandle_t)((int)cache&0xffff) ); +} + diff --git a/tier3/tier3.cpp b/tier3/tier3.cpp new file mode 100644 index 0000000..20241a6 --- /dev/null +++ b/tier3/tier3.cpp @@ -0,0 +1,154 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: A higher level link library for general use in the game and tools. +// +//===========================================================================// + +#include "tier3/tier3.h" +#include "tier0/dbg.h" +#include "istudiorender.h" +#include "vgui/IVGui.h" +#include "vgui/IInput.h" +#include "vgui/IPanel.h" +#include "vgui/ISurface.h" +#include "vgui/ILocalize.h" +#include "vgui/IScheme.h" +#include "vgui/ISystem.h" +#include "VGuiMatSurface/IMatSystemSurface.h" +#include "datacache/idatacache.h" +#include "datacache/imdlcache.h" +#include "video/ivideoservices.h" +#include "movieobjects/idmemakefileutils.h" +#include "vphysics_interface.h" +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "ivtex.h" + + +//----------------------------------------------------------------------------- +// These tier3 libraries must be set by any users of this library. +// They can be set by calling ConnectTier3Libraries. +// It is hoped that setting this, and using this library will be the common mechanism for +// allowing link libraries to access tier3 library interfaces +//----------------------------------------------------------------------------- +IStudioRender *g_pStudioRender = 0; +IStudioRender *studiorender = 0; +IMatSystemSurface *g_pMatSystemSurface = 0; +vgui::IInput *g_pVGuiInput = 0; +vgui::ISurface *g_pVGuiSurface = 0; +vgui::IPanel *g_pVGuiPanel = 0; +vgui::IVGui *g_pVGui = 0; +vgui::ILocalize *g_pVGuiLocalize = 0; +vgui::ISchemeManager *g_pVGuiSchemeManager = 0; +vgui::ISystem *g_pVGuiSystem = 0; +IDataCache *g_pDataCache = 0; +IMDLCache *g_pMDLCache = 0; +IMDLCache *mdlcache = 0; +IVideoServices *g_pVideo = NULL; +IDmeMakefileUtils *g_pDmeMakefileUtils = 0; +IPhysicsCollision *g_pPhysicsCollision = 0; +ISoundEmitterSystemBase *g_pSoundEmitterSystem = 0; +IVTex *g_pVTex = 0; + + +//----------------------------------------------------------------------------- +// Call this to connect to all tier 3 libraries. +// It's up to the caller to check the globals it cares about to see if ones are missing +//----------------------------------------------------------------------------- +void ConnectTier3Libraries( CreateInterfaceFn *pFactoryList, int nFactoryCount ) +{ + // Don't connect twice.. + Assert( !g_pStudioRender && !studiorender && !g_pMatSystemSurface && !g_pVGui && !g_pVGuiPanel && !g_pVGuiInput && + !g_pVGuiSurface && !g_pDataCache && !g_pMDLCache && !mdlcache && !g_pVideo && + !g_pDmeMakefileUtils && !g_pPhysicsCollision && !g_pVGuiLocalize && !g_pSoundEmitterSystem && + !g_pVGuiSchemeManager && !g_pVGuiSystem ); + + for ( int i = 0; i < nFactoryCount; ++i ) + { + if ( !g_pStudioRender ) + { + g_pStudioRender = studiorender = ( IStudioRender * )pFactoryList[i]( STUDIO_RENDER_INTERFACE_VERSION, NULL ); + } + if ( !g_pVGui ) + { + g_pVGui = (vgui::IVGui*)pFactoryList[i]( VGUI_IVGUI_INTERFACE_VERSION, NULL ); + } + if ( !g_pVGuiInput ) + { + g_pVGuiInput = (vgui::IInput*)pFactoryList[i]( VGUI_INPUT_INTERFACE_VERSION, NULL ); + } + if ( !g_pVGuiPanel ) + { + g_pVGuiPanel = (vgui::IPanel*)pFactoryList[i]( VGUI_PANEL_INTERFACE_VERSION, NULL ); + } + if ( !g_pVGuiSurface ) + { + g_pVGuiSurface = (vgui::ISurface*)pFactoryList[i]( VGUI_SURFACE_INTERFACE_VERSION, NULL ); + } + if ( !g_pVGuiSchemeManager ) + { + g_pVGuiSchemeManager = (vgui::ISchemeManager*)pFactoryList[i]( VGUI_SCHEME_INTERFACE_VERSION, NULL ); + } + if ( !g_pVGuiSystem ) + { + g_pVGuiSystem = (vgui::ISystem*)pFactoryList[i]( VGUI_SYSTEM_INTERFACE_VERSION, NULL ); + } + if ( !g_pVGuiLocalize ) + { + g_pVGuiLocalize = (vgui::ILocalize*)pFactoryList[i]( VGUI_LOCALIZE_INTERFACE_VERSION, NULL ); + } + if ( !g_pMatSystemSurface ) + { + g_pMatSystemSurface = ( IMatSystemSurface * )pFactoryList[i]( MAT_SYSTEM_SURFACE_INTERFACE_VERSION, NULL ); + } + if ( !g_pDataCache ) + { + g_pDataCache = (IDataCache*)pFactoryList[i]( DATACACHE_INTERFACE_VERSION, NULL ); + } + if ( !g_pMDLCache ) + { + g_pMDLCache = mdlcache = (IMDLCache*)pFactoryList[i]( MDLCACHE_INTERFACE_VERSION, NULL ); + } + if ( !g_pVideo ) + { + g_pVideo = (IVideoServices *)pFactoryList[i](VIDEO_SERVICES_INTERFACE_VERSION, NULL); + } + if ( !g_pDmeMakefileUtils ) + { + g_pDmeMakefileUtils = (IDmeMakefileUtils*)pFactoryList[i]( DMEMAKEFILE_UTILS_INTERFACE_VERSION, NULL ); + } + if ( !g_pPhysicsCollision ) + { + g_pPhysicsCollision = ( IPhysicsCollision* )pFactoryList[i]( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + } + if ( !g_pSoundEmitterSystem ) + { + g_pSoundEmitterSystem = ( ISoundEmitterSystemBase* )pFactoryList[i]( SOUNDEMITTERSYSTEM_INTERFACE_VERSION, NULL ); + } + if ( !g_pVTex ) + { + g_pVTex = ( IVTex * )pFactoryList[i]( IVTEX_VERSION_STRING, NULL ); + } + } +} + +void DisconnectTier3Libraries() +{ + g_pStudioRender = 0; + studiorender = 0; + g_pVGui = 0; + g_pVGuiInput = 0; + g_pVGuiPanel = 0; + g_pVGuiSurface = 0; + g_pVGuiLocalize = 0; + g_pVGuiSchemeManager = 0; + g_pVGuiSystem = 0; + g_pMatSystemSurface = 0; + g_pDataCache = 0; + g_pMDLCache = 0; + mdlcache = 0; + g_pVideo = NULL; + g_pPhysicsCollision = 0; + g_pDmeMakefileUtils = NULL; + g_pSoundEmitterSystem = 0; + g_pVTex = NULL; +} diff --git a/tier3/tier3.vpc b/tier3/tier3.vpc new file mode 100644 index 0000000..ccec731 --- /dev/null +++ b/tier3/tier3.vpc @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------------- +// TIER3.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$macro SRCDIR ".." + +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Project "tier3" +{ + $Folder "Source Files" + { + $File "tier3.cpp" + $File "mdlutils.cpp" + $File "choreoutils.cpp" + $File "scenetokenprocessor.cpp" + $File "studiohdrstub.cpp" + } + + $Folder "Header Files" + { + $File "$SRCDIR\public\tier3\tier3.h" + $File "$SRCDIR\public\tier3\mdlutils.h" + $File "$SRCDIR\public\tier3\choreoutils.h" + $File "$SRCDIR\public\tier3\scenetokenprocessor.h" + } +} diff --git a/vpc_scripts/projects.vgc b/vpc_scripts/projects.vgc index 6e8888b..c115419 100644 --- a/vpc_scripts/projects.vgc +++ b/vpc_scripts/projects.vgc @@ -54,7 +54,7 @@ $Project "game_shader_dx9" $Project "glview" { - "utils\glview\glview.vpc" [$WIN32] + "utils\glview\glview.vpc" [$WINDOWS] } $Project "launcher" @@ -74,15 +74,15 @@ $Project "gameui" $Project "height2normal" { - "utils\height2normal\height2normal.vpc" [$WIN32] + "utils\height2normal\height2normal.vpc" [$WINDOWS] } $Project "server" { - "game\server\server_hl2mp.vpc" [($WIN32||$POSIX) && $HL2MP] - "game\server\server_episodic.vpc" [($WIN32||$POSIX) && $EPISODIC] - "game\server\server_hl2.vpc" [($WIN32||$POSIX) && $HL2] - "game\server\server_sdk.vpc" [($WIN32||$POSIX) && $SDK] + "game\server\server_hl2mp.vpc" [($WINDOWS||$POSIX) && $HL2MP] + "game\server\server_episodic.vpc" [($WINDOWS||$POSIX) && $EPISODIC] + "game\server\server_hl2.vpc" [($WINDOWS||$POSIX) && $HL2] + "game\server\server_sdk.vpc" [($WINDOWS||$POSIX) && $SDK] } $Project "mathlib" @@ -92,12 +92,12 @@ $Project "mathlib" $Project "motionmapper" { - "utils\motionmapper\motionmapper.vpc" [$WIN32] + "utils\motionmapper\motionmapper.vpc" [$WINDOWS] } $Project "phonemeextractor" { - "utils\phonemeextractor\phonemeextractor.vpc" [$WIN32] + "utils\phonemeextractor\phonemeextractor.vpc" [$WINDOWS] } $Project "imgui" @@ -111,22 +111,22 @@ $Project "particles" $Project "raytrace" { - "raytrace\raytrace.vpc" [$WIN32||$X360||$POSIX] + "raytrace\raytrace.vpc" [$WINDOWS||$X360||$POSIX] } $Project "qc_eyes" { - "utils\qc_eyes\qc_eyes.vpc" [$WIN32] + "utils\qc_eyes\qc_eyes.vpc" [$WINDOWS] } $Project "serverplugin_empty" { - "utils\serverplugin_sample\serverplugin_empty.vpc" [$WIN32||$POSIX] + "utils\serverplugin_sample\serverplugin_empty.vpc" [$WINDOWS||$POSIX] } $Project "scenefilecache" { - "scenefilecache\scenefilecache.vpc" [$WIN32||$POSIX] + "scenefilecache\scenefilecache.vpc" [$WINDOWS||$POSIX] } $Project "shaderapidx9" @@ -145,22 +145,22 @@ $Project "shaderapidxvk" } $Project "stdshader_dbg" { - "materialsystem\stdshaders\stdshader_dbg.vpc" [$WIN32||$X360||$POSIX] + "materialsystem\stdshaders\stdshader_dbg.vpc" [$WINDOWS||$X360||$POSIX] } $Project "stdshader_dx6" { - "materialsystem\stdshaders\stdshader_dx6.vpc" [$WIN32] + "materialsystem\stdshaders\stdshader_dx6.vpc" [$WINDOWS] } $Project "stdshader_dx7" { - "materialsystem\stdshaders\stdshader_dx7.vpc" [$WIN32] + "materialsystem\stdshaders\stdshader_dx7.vpc" [$WINDOWS] } $Project "stdshader_dx8" { - "materialsystem\stdshaders\stdshader_dx8.vpc" [$WIN32] + "materialsystem\stdshaders\stdshader_dx8.vpc" [$WINDOWS] } $Project "stdshader_dx9" @@ -180,7 +180,12 @@ $Project "shaderlib_dx11" $Project "tgadiff" { - "utils\tgadiff\tgadiff.vpc" [$WIN32] + "utils\tgadiff\tgadiff.vpc" [$WINDOWS] +} + +$Project "tier0" +{ + "tier0\tier0.vpc" [$WINDOWS || $X360||$POSIX] } $Project "tier1" @@ -188,9 +193,19 @@ $Project "tier1" "tier1\tier1.vpc" [$WINDOWS || $X360||$POSIX] } +$Project "tier2" +{ + "tier2\tier2.vpc" [$WINDOWS || $X360||$POSIX] +} + +$Project "tier3" +{ + "tier3\tier3.vpc" [$WINDOWS || $X360||$POSIX] +} + $Project "vbsp" { - "utils\vbsp\vbsp.vpc" [$WIN32] + "utils\vbsp\vbsp.vpc" [$WINDOWS] } $Project "vgui_controls" @@ -200,80 +215,85 @@ $Project "vgui_controls" $Project "vice" { - "utils\vice\vice.vpc" [$WIN32] + "utils\vice\vice.vpc" [$WINDOWS] } $Project "vrad_dll" { - "utils\vrad\vrad_dll.vpc" [$WIN32] + "utils\vrad\vrad_dll.vpc" [$WINDOWS] } $Project "vrad_launcher" { - "utils\vrad_launcher\vrad_launcher.vpc" [$WIN32] + "utils\vrad_launcher\vrad_launcher.vpc" [$WINDOWS] } $Project "vtf2tga" { - "utils\vtf2tga\vtf2tga.vpc" [$WIN32] + "utils\vtf2tga\vtf2tga.vpc" [$WINDOWS] } $Project "vtfdiff" { - "utils\vtfdiff\vtfdiff.vpc" [$WIN32] + "utils\vtfdiff\vtfdiff.vpc" [$WINDOWS] +} + +$Project "vstdlib" +{ + "vstdlib\vstdlib.vpc" [$WINDOWS] } $Project "vvis_dll" { - "utils\vvis\vvis_dll.vpc" [$WIN32] + "utils\vvis\vvis_dll.vpc" [$WINDOWS] } $Project "vvis_launcher" { - "utils\vvis_launcher\vvis_launcher.vpc" [$WIN32] + "utils\vvis_launcher\vvis_launcher.vpc" [$WINDOWS] } $Project "vpklib" { - "vpklib/vpklib.vpc" [$WIN32] + "vpklib/vpklib.vpc" [$WINDOWS] } $Project "vtf" { - "vtf/vtf.vpc" [$WIN32] + "vtf/vtf.vpc" [$WINDOWS] } $Project "engine" { - "engine/engine.vpc" [$WIN32] + "engine/engine.vpc" [$WINDOWS] } $Project "materialsystem" { - "materialsystem/materialsystem.vpc" [$WIN32] + "materialsystem/materialsystem.vpc" [$WINDOWS] } $Project "studiorender" { - "studiorender\studiorender.vpc" [$WIN32] + "studiorender\studiorender.vpc" [$WINDOWS] } $Project "filesystem_stdio" { - "filesystem/filesystem_stdio.vpc" [$WIN32] + "filesystem/filesystem_stdio.vpc" [$WINDOWS] } $Project "filesystem_steam" { - "filesystem/filesystem_steam.vpc" [$WIN32] + "filesystem/filesystem_steam.vpc" [$WINDOWS] } $Project "bsppack" { - "utils\bsppack\bsppack.vpc" [$WIN32] + "utils\bsppack\bsppack.vpc" [$WINDOWS] } $Project "bspzip" { - "utils\bspzip\bspzip.vpc" [$WIN32] + "utils\bspzip\bspzip.vpc" [$WINDOWS] } \ No newline at end of file diff --git a/vstdlib/KeyValuesSystem.cpp b/vstdlib/KeyValuesSystem.cpp new file mode 100644 index 0000000..0665d94 --- /dev/null +++ b/vstdlib/KeyValuesSystem.cpp @@ -0,0 +1,416 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include +#include +#include "mempool.h" +#include "utlsymbol.h" +#include "tier0/threadtools.h" +#include "tier1/memstack.h" +#include "tier1/utlmap.h" +#include "tier1/utlstring.h" +#include "tier1/fmtstr.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include + +#ifdef NO_SBH // no need to pool if using tier0 small block heap +#define KEYVALUES_USE_POOL 1 +#endif + +//----------------------------------------------------------------------------- +// Purpose: Central storage point for KeyValues memory and symbols +//----------------------------------------------------------------------------- +class CKeyValuesSystem : public IKeyValuesSystem +{ +public: + CKeyValuesSystem(); + ~CKeyValuesSystem(); + + // registers the size of the KeyValues in the specified instance + // so it can build a properly sized memory pool for the KeyValues objects + // the sizes will usually never differ but this is for versioning safety + void RegisterSizeofKeyValues(int size); + + // allocates/frees a KeyValues object from the shared mempool + void *AllocKeyValuesMemory(int size); + void FreeKeyValuesMemory(void *pMem); + + // symbol table access (used for key names) + HKeySymbol GetSymbolForString( const char *name, bool bCreate ); + const char *GetStringForSymbol(HKeySymbol symbol); + + // returns the wide version of ansi, also does the lookup on #'d strings + void GetLocalizedFromANSI( const char *ansi, wchar_t *outBuf, int unicodeBufferSizeInBytes); + void GetANSIFromLocalized( const wchar_t *wchar, char *outBuf, int ansiBufferSizeInBytes ); + + // for debugging, adds KeyValues record into global list so we can track memory leaks + virtual void AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name); + virtual void RemoveKeyValuesFromMemoryLeakList(void *pMem); + + // maintain a cache of KeyValues we load from disk. This saves us quite a lot of time on app startup. + virtual void AddFileKeyValuesToCache( const KeyValues* _kv, const char *resourceName, const char *pathID ); + virtual bool LoadFileKeyValuesFromCache( KeyValues* outKv, const char *resourceName, const char *pathID, IBaseFileSystem *filesystem ) const; + virtual void InvalidateCache(); + virtual void InvalidateCacheForFile( const char *resourceName, const char *pathID ); + +private: +#ifdef KEYVALUES_USE_POOL + CUtlMemoryPool *m_pMemPool; +#endif + int m_iMaxKeyValuesSize; + + // string hash table + CMemoryStack m_Strings; + struct hash_item_t + { + int stringIndex; + hash_item_t *next; + }; + CUtlMemoryPool m_HashItemMemPool; + CUtlVector m_HashTable; + int CaseInsensitiveHash(const char *string, int iBounds); + + void DoInvalidateCache(); + + struct MemoryLeakTracker_t + { + int nameIndex; + void *pMem; + }; + static bool MemoryLeakTrackerLessFunc( const MemoryLeakTracker_t &lhs, const MemoryLeakTracker_t &rhs ) + { + return lhs.pMem < rhs.pMem; + } + CUtlRBTree m_KeyValuesTrackingList; + + CThreadFastMutex m_mutex; + + CUtlMap m_KeyValueCache; +}; + +// EXPOSE_SINGLE_INTERFACE(CKeyValuesSystem, IKeyValuesSystem, KEYVALUES_INTERFACE_VERSION); + +//----------------------------------------------------------------------------- +// Instance singleton and expose interface to rest of code +//----------------------------------------------------------------------------- +static CKeyValuesSystem g_KeyValuesSystem; + +IKeyValuesSystem *KeyValuesSystem() +{ + return &g_KeyValuesSystem; +} + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CKeyValuesSystem::CKeyValuesSystem() +: m_HashItemMemPool(sizeof(hash_item_t), 64, UTLMEMORYPOOL_GROW_FAST, "CKeyValuesSystem::m_HashItemMemPool") +, m_KeyValuesTrackingList(0, 0, MemoryLeakTrackerLessFunc) +, m_KeyValueCache( UtlStringLessFunc ) +{ + // initialize hash table + m_HashTable.AddMultipleToTail(2047); + for (int i = 0; i < m_HashTable.Count(); i++) + { + m_HashTable[i].stringIndex = 0; + m_HashTable[i].next = NULL; + } + + m_Strings.Init( 4*1024*1024, 64*1024, 0, 4 ); + char *pszEmpty = ((char *)m_Strings.Alloc(1)); + *pszEmpty = 0; + +#ifdef KEYVALUES_USE_POOL + m_pMemPool = NULL; +#endif + m_iMaxKeyValuesSize = sizeof(KeyValues); +} + +//----------------------------------------------------------------------------- +// Purpose: Destructor +//----------------------------------------------------------------------------- +CKeyValuesSystem::~CKeyValuesSystem() +{ +#ifdef KEYVALUES_USE_POOL +#ifdef _DEBUG + // display any memory leaks + if (m_pMemPool && m_pMemPool->Count() > 0) + { + DevMsg("Leaked KeyValues blocks: %d\n", m_pMemPool->Count()); + } + + // iterate all the existing keyvalues displaying their names + for (int i = 0; i < m_KeyValuesTrackingList.MaxElement(); i++) + { + if (m_KeyValuesTrackingList.IsValidIndex(i)) + { + DevMsg("\tleaked KeyValues(%s)\n", &m_Strings[m_KeyValuesTrackingList[i].nameIndex]); + } + } +#endif + + delete m_pMemPool; +#endif + + DoInvalidateCache(); +} + +//----------------------------------------------------------------------------- +// Purpose: registers the size of the KeyValues in the specified instance +// so it can build a properly sized memory pool for the KeyValues objects +// the sizes will usually never differ but this is for versioning safety +//----------------------------------------------------------------------------- +void CKeyValuesSystem::RegisterSizeofKeyValues(int size) +{ + if (size > m_iMaxKeyValuesSize) + { + m_iMaxKeyValuesSize = size; + } +} + +#ifdef KEYVALUES_USE_POOL +static void KVLeak( char const *fmt, ... ) +{ + va_list argptr; + char data[1024]; + + va_start(argptr, fmt); + Q_vsnprintf(data, sizeof( data ), fmt, argptr); + va_end(argptr); + + Msg( "%s", data ); +} +#endif + +//----------------------------------------------------------------------------- +// Purpose: allocates a KeyValues object from the shared mempool +//----------------------------------------------------------------------------- +void *CKeyValuesSystem::AllocKeyValuesMemory(int size) +{ +#ifdef KEYVALUES_USE_POOL + // allocate, if we don't have one yet + if (!m_pMemPool) + { + m_pMemPool = new CUtlMemoryPool(m_iMaxKeyValuesSize, 1024, UTLMEMORYPOOL_GROW_FAST, "CKeyValuesSystem::m_pMemPool" ); + m_pMemPool->SetErrorReportFunc( KVLeak ); + } + + return m_pMemPool->Alloc(size); +#else + return malloc( size ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: frees a KeyValues object from the shared mempool +//----------------------------------------------------------------------------- +void CKeyValuesSystem::FreeKeyValuesMemory(void *pMem) +{ +#ifdef KEYVALUES_USE_POOL + m_pMemPool->Free(pMem); +#else + free( pMem ); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: symbol table access (used for key names) +//----------------------------------------------------------------------------- +HKeySymbol CKeyValuesSystem::GetSymbolForString( const char *name, bool bCreate ) +{ + if ( !name ) + { + return (-1); + } + + AUTO_LOCK( m_mutex ); + + int hash = CaseInsensitiveHash(name, m_HashTable.Count()); + int i = 0; + hash_item_t *item = &m_HashTable[hash]; + while (1) + { + if (!stricmp(name, (char *)m_Strings.GetBase() + item->stringIndex )) + { + return (HKeySymbol)item->stringIndex; + } + + i++; + + if (item->next == NULL) + { + if ( !bCreate ) + { + // not found + return -1; + } + + // we're not in the table + if (item->stringIndex != 0) + { + // first item is used, an new item + item->next = (hash_item_t *)m_HashItemMemPool.Alloc(sizeof(hash_item_t)); + item = item->next; + } + + // build up the new item + item->next = NULL; + char *pString = (char *)m_Strings.Alloc( V_strlen(name) + 1 ); + if ( !pString ) + { + Error( "Out of keyvalue string space" ); + return -1; + } + item->stringIndex = pString - (char *)m_Strings.GetBase(); + strcpy(pString, name); + return (HKeySymbol)item->stringIndex; + } + + item = item->next; + } + + // shouldn't be able to get here + Assert(0); + return (-1); +} + +//----------------------------------------------------------------------------- +// Purpose: symbol table access +//----------------------------------------------------------------------------- +const char *CKeyValuesSystem::GetStringForSymbol(HKeySymbol symbol) +{ + if ( symbol == -1 ) + { + return ""; + } + return ((char *)m_Strings.GetBase() + (size_t)symbol); +} + +//----------------------------------------------------------------------------- +// Purpose: adds KeyValues record into global list so we can track memory leaks +//----------------------------------------------------------------------------- +void CKeyValuesSystem::AddKeyValuesToMemoryLeakList(void *pMem, HKeySymbol name) +{ +#ifdef _DEBUG + // only track the memory leaks in debug builds + MemoryLeakTracker_t item = { name, pMem }; + m_KeyValuesTrackingList.Insert(item); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: used to track memory leaks +//----------------------------------------------------------------------------- +void CKeyValuesSystem::RemoveKeyValuesFromMemoryLeakList(void *pMem) +{ +#ifdef _DEBUG + // only track the memory leaks in debug builds + MemoryLeakTracker_t item = { 0, pMem }; + int index = m_KeyValuesTrackingList.Find(item); + m_KeyValuesTrackingList.RemoveAt(index); +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Removes a particular key value file (from a particular source) from the cache if present. +//----------------------------------------------------------------------------- +void CKeyValuesSystem::InvalidateCacheForFile(const char *resourceName, const char *pathID) +{ + CUtlString identString( CFmtStr( "%s::%s", resourceName ? resourceName : "", pathID ? pathID : "" ) ); + + CUtlMap::IndexType_t index = m_KeyValueCache.Find( identString ); + if ( m_KeyValueCache.IsValidIndex( index ) ) + { + m_KeyValueCache[ index ]->deleteThis(); + m_KeyValueCache.RemoveAt( index ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Adds a particular key value file (from a particular source) to the cache if not already present. +//----------------------------------------------------------------------------- +void CKeyValuesSystem::AddFileKeyValuesToCache(const KeyValues* _kv, const char *resourceName, const char *pathID) +{ + CUtlString identString( CFmtStr( "%s::%s", resourceName ? resourceName : "", pathID ? pathID : "" ) ); + // Some files actually have multiple roots, and if you use regular MakeCopy (without passing true), those + // will be missed. This caused a bug in soundscapes on dedicated servers. + m_KeyValueCache.Insert( identString, _kv->MakeCopy( true ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: Fetches a particular keyvalue from the cache, and copies into _outKv (clearing anything there already). +//----------------------------------------------------------------------------- +bool CKeyValuesSystem::LoadFileKeyValuesFromCache(KeyValues* outKv, const char *resourceName, const char *pathID, IBaseFileSystem *filesystem) const +{ + Assert( outKv ); + Assert( resourceName ); + + COM_TimestampedLog("CKeyValuesSystem::LoadFileKeyValuesFromCache(%s%s%s): Begin", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : ""); + + CUtlString identString(CFmtStr("%s::%s", resourceName ? resourceName : "", pathID ? pathID : "")); + + CUtlMap::IndexType_t index = m_KeyValueCache.Find( identString ); + + if ( m_KeyValueCache.IsValidIndex( index ) ) { + (*outKv) = ( *m_KeyValueCache[ index ] ); + COM_TimestampedLog("CKeyValuesSystem::LoadFileKeyValuesFromCache(%s%s%s): End / Hit", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : ""); + return true; + } + + COM_TimestampedLog("CKeyValuesSystem::LoadFileKeyValuesFromCache(%s%s%s): End / Miss", pathID ? pathID : "", pathID && resourceName ? "/" : "", resourceName ? resourceName : ""); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Evicts everything from the cache, cleans up the memory used. +//----------------------------------------------------------------------------- +void CKeyValuesSystem::InvalidateCache() +{ + DoInvalidateCache(); +} + +//----------------------------------------------------------------------------- +// Purpose: generates a simple hash value for a string +//----------------------------------------------------------------------------- +int CKeyValuesSystem::CaseInsensitiveHash(const char *string, int iBounds) +{ + unsigned int hash = 0; + + for ( ; *string != 0; string++ ) + { + if (*string >= 'A' && *string <= 'Z') + { + hash = (hash << 1) + (*string - 'A' + 'a'); + } + else + { + hash = (hash << 1) + *string; + } + } + + return hash % iBounds; +} + +//----------------------------------------------------------------------------- +// Purpose: Evicts everything from the cache, cleans up the memory used. +//----------------------------------------------------------------------------- +void CKeyValuesSystem::DoInvalidateCache() +{ + // Cleanup the cache. + FOR_EACH_MAP_FAST( m_KeyValueCache, mapIndex ) + { + m_KeyValueCache[mapIndex]->deleteThis(); + } + + // Apparently you cannot call RemoveAll on a map without also purging the contents because... ? + // If you do and you continue to use the map, you will eventually wind up in a case where you + // have an empty map but it still iterates over elements. Awesome? + m_KeyValueCache.Purge(); +} + diff --git a/vstdlib/coroutine.cpp b/vstdlib/coroutine.cpp new file mode 100644 index 0000000..5cabe93 --- /dev/null +++ b/vstdlib/coroutine.cpp @@ -0,0 +1,1157 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// Build Notes: In order for the coroutine system to work a few build options +// need to be set for coroutine.cpp itself. These are the VPC +// entries for those options: +// $Compiler +// { +// $EnableC++Exceptions "No" +// $BasicRuntimeChecks "Default" +// $EnableFloatingPointExceptions "No" +// } +// +// If you have not set these options you will get a strange popup in +// Visual Studio at the end of Coroutine_Continue(). +// +//============================================================================= + +//#include "pch_vstdlib.h" +#if defined(_DEBUG) +// Verify that something is false +#define DbgVerifyNot(x) Assert(!x) +#else +#define DbgVerifyNot(x) x +#endif + +#include "vstdlib/coroutine.h" +#include "tier0/vprof.h" +#include "tier0/minidump.h" +#include "tier1/utllinkedlist.h" +#include "tier1/utlvector.h" +#include + +// for debugging +//#define CHECK_STACK_CORRUPTION + + +#ifndef STEAM +#define PvAlloc(x) malloc(x) +#define FreePv(x) free(x) +#endif + +#ifdef CHECK_STACK_CORRUPTION +#include "tier1/checksum_md5.h" +#include "../tier1/checksum_md5.cpp" +#endif // CHECK_STACK_CORRUPTION + +//#define COROUTINE_TRACE +#ifdef COROUTINE_TRACE +#include "tier1/fmtstr.h" +static CFmtStr g_fmtstr; +#ifdef WIN32 +extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char * ); +#else +void OutputDebugStringA( const char *pchMsg ) { fprintf( stderr, pchMsg ); fflush( stderr ); } +#endif +#define CoroutineDbgMsg( fmt, ... ) \ +{ \ + g_fmtstr.sprintf( fmt, ##__VA_ARGS__ ); \ + OutputDebugStringA( g_fmtstr ); \ +} +#else +#define CoroutineDbgMsg( pchMsg, ... ) +#endif // COROUTINE_TRACE + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if defined( _MSC_VER ) && ( _MSC_VER >= 1900 ) && defined( PLATFORM_64BITS ) +//the VS2105 longjmp() seems to freak out jumping back into a coroutine (just like linux if _FORTIFY_SOURCE is defined) +// I can't find an analogy to _FORTIFY_SOURCE for MSVC at the moment, so I wrote a quick assembly to longjmp() without any safety checks +extern "C" NORETURN void Coroutine_LongJmp_Unchecked( jmp_buf buffer, int nResult ); +#define Coroutine_longjmp Coroutine_LongJmp_Unchecked + +#ifdef _WIN64 +#define Q_offsetof(s,m) (size_t)( (ptrdiff_t)&reinterpret_cast((((s *)0)->m)) ) +#else +#define Q_offsetof(s,m) (size_t)&reinterpret_cast((((s *)0)->m)) +#endif +#define SIZEOF_MEMBER( className, memberName ) sizeof( ((className*)nullptr)->memberName ) + + +#define Validate_Jump_Buffer( _Member ) COMPILE_TIME_ASSERT( (Q_offsetof( _JUMP_BUFFER, _Member ) == Q_offsetof( _Duplicate_JUMP_BUFFER, _Member )) && (SIZEOF_MEMBER( _JUMP_BUFFER, _Member ) == SIZEOF_MEMBER( _Duplicate_JUMP_BUFFER, _Member )) ) + + //validate that the structure in assembly matches what the crt setjmp thinks it is +# if defined( PLATFORM_64BITS ) + struct _Duplicate_JUMP_BUFFER + { + unsigned __int64 Frame; + unsigned __int64 Rbx; + unsigned __int64 Rsp; + unsigned __int64 Rbp; + unsigned __int64 Rsi; + unsigned __int64 Rdi; + unsigned __int64 R12; + unsigned __int64 R13; + unsigned __int64 R14; + unsigned __int64 R15; + unsigned __int64 Rip; + unsigned long MxCsr; + unsigned short FpCsr; + unsigned short Spare; + + SETJMP_FLOAT128 Xmm6; + SETJMP_FLOAT128 Xmm7; + SETJMP_FLOAT128 Xmm8; + SETJMP_FLOAT128 Xmm9; + SETJMP_FLOAT128 Xmm10; + SETJMP_FLOAT128 Xmm11; + SETJMP_FLOAT128 Xmm12; + SETJMP_FLOAT128 Xmm13; + SETJMP_FLOAT128 Xmm14; + SETJMP_FLOAT128 Xmm15; + }; + + COMPILE_TIME_ASSERT( sizeof( _JUMP_BUFFER ) == sizeof( _Duplicate_JUMP_BUFFER ) ); + Validate_Jump_Buffer( Frame ); + Validate_Jump_Buffer( Rbx ); + Validate_Jump_Buffer( Rsp ); + Validate_Jump_Buffer( Rbp ); + Validate_Jump_Buffer( Rsi ); + Validate_Jump_Buffer( Rdi ); + Validate_Jump_Buffer( R12 ); + Validate_Jump_Buffer( R13 ); + Validate_Jump_Buffer( R14 ); + Validate_Jump_Buffer( R15 ); + Validate_Jump_Buffer( Rip ); + Validate_Jump_Buffer( MxCsr ); + Validate_Jump_Buffer( FpCsr ); + Validate_Jump_Buffer( Spare ); + + Validate_Jump_Buffer( Xmm6 ); + Validate_Jump_Buffer( Xmm7 ); + Validate_Jump_Buffer( Xmm8 ); + Validate_Jump_Buffer( Xmm9 ); + Validate_Jump_Buffer( Xmm10 ); + Validate_Jump_Buffer( Xmm11 ); + Validate_Jump_Buffer( Xmm12 ); + Validate_Jump_Buffer( Xmm13 ); + Validate_Jump_Buffer( Xmm14 ); + Validate_Jump_Buffer( Xmm15 ); +# else + struct _Duplicate_JUMP_BUFFER + { + unsigned long Ebp; + unsigned long Ebx; + unsigned long Edi; + unsigned long Esi; + unsigned long Esp; + unsigned long Eip; + unsigned long Registration; + unsigned long TryLevel; + unsigned long Cookie; + unsigned long UnwindFunc; + unsigned long UnwindData[6]; + }; + + COMPILE_TIME_ASSERT( sizeof( _JUMP_BUFFER ) == sizeof( _Duplicate_JUMP_BUFFER ) ); + + Validate_Jump_Buffer( Ebp ); + Validate_Jump_Buffer( Ebx ); + Validate_Jump_Buffer( Edi ); + Validate_Jump_Buffer( Esi ); + Validate_Jump_Buffer( Esp ); + Validate_Jump_Buffer( Eip ); + Validate_Jump_Buffer( Registration ); + Validate_Jump_Buffer( TryLevel ); + Validate_Jump_Buffer( Cookie ); + Validate_Jump_Buffer( UnwindFunc ); + Validate_Jump_Buffer( UnwindData[6] ); +# endif + +#else +#define Coroutine_longjmp longjmp +#endif + + +// it *feels* like we should need barriers around our setjmp/longjmp calls, and the memcpy's +// to make sure the optimizer doesn't reorder us across register load/stores, so I've put them +// in what seem like the appropriate spots, but we seem to run ok without them, so... +#ifdef GNUC +#define RW_MEMORY_BARRIER /* __sync_synchronize() */ +#else +#define RW_MEMORY_BARRIER /* _ReadWriteBarrier() */ +#endif + + + +// return values from setjmp() +static const int k_iSetJmpStateSaved = 0x00; +static const int k_iSetJmpContinue = 0x01; +static const int k_iSetJmpDone = 0x02; +static const int k_iSetJmpDbgBreak = 0x03; + +// distance up the stack that coroutine functions stacks' start +#ifdef _PS3 +// PS3 has a small stack. Hopefully we dont need 64k of padding! +static const int k_cubCoroutineStackGap = (3 * 1024); +static const int k_cubCoroutineStackGapSmall = 64; +#else +static const int k_cubCoroutineStackGap = (64 * 1024); +static const int k_cubCoroutineStackGapSmall = 64; +#endif + +// Warning size for allocated stacks +#ifdef _DEBUG +// In debug builds, we'll end up with much more stack usage in some scenarios that isn't representative of release +// builds. We should still warn if we're going way above what we could expect the optimizer to save us from, but the +// warning is more salient in release. +static const int k_cubMaxCoroutineStackSize = (48 * 1024); +#else +static const int k_cubMaxCoroutineStackSize = (32 * 1024); +#endif // defined( _DEBUG ) + +#ifdef _WIN64 +extern "C" byte *GetStackPtr64(); +#define GetStackPtr( pStackPtr) byte *pStackPtr = GetStackPtr64(); +#else +#ifdef WIN32 +#define GetStackPtr( pStackPtr ) byte *pStackPtr; __asm mov pStackPtr, esp +#elif defined(GNUC) +// Apple's version of gcc/g++ doesn't return the expected value using the intrinsic, so +// do it the old fashioned way - this will also use asm on linux (since we don't compile +// with llvm/clang there) but that seems fine. +#if defined(__llvm__) || defined(__clang__) +#define GetStackPtr( pStackPtr ) byte *pStackPtr = (byte*)__builtin_frame_address(0) +#else +#define GetStackPtr( pStackPtr ) register byte *pStackPtr __asm__( "esp" ) +#endif +#elif defined(__SNC__) +#define GetStackPtr( pStackPtr ) byte *pStackPtr = (byte*)__builtin_frame_address(0) +#else +#error +#endif +#endif + +#ifdef _M_X64 +#define _REGISTER_ALIGNMENT 16ull + +int CalcAlignOffset( const unsigned char *p ) +{ + return static_cast( AlignValue( p, _REGISTER_ALIGNMENT ) - p ); +} + +#endif + + +//----------------------------------------------------------------------------- +// Purpose: single coroutine descriptor +//----------------------------------------------------------------------------- +#if defined( _PS3 ) && defined( _DEBUG ) +byte rgStackTempBuffer[65535]; +#endif +class CCoroutine +{ +public: + + CCoroutine() + { + m_pSavedStack = NULL; + m_pStackHigh = m_pStackLow = NULL; + m_cubSavedStack = 0; + m_pFunc = NULL; + m_pchName = "(none)"; + m_iJumpCode = 0; + m_pchDebugMsg = NULL; +#ifdef COROUTINE_TRACE + m_hCoroutine = -1; +#endif +#ifdef _M_X64 + m_nAlignmentBytes = CalcAlignOffset( m_rgubRegisters ); +#endif +#if defined( VPROF_ENABLED ) + m_pVProfNodeScope = NULL; +#endif + } + + jmp_buf &GetRegisters() + { +#ifdef _M_X64 + // Did we get moved in memory in such a way that the registers became unaligned? + // If so, fix them up now + size_t align = _REGISTER_ALIGNMENT - 1; + unsigned char *pRegistersCur = &m_rgubRegisters[m_nAlignmentBytes]; + if ( (size_t)pRegistersCur & align ) + { + m_nAlignmentBytes = CalcAlignOffset( m_rgubRegisters ); + unsigned char *pRegistersNew = &m_rgubRegisters[m_nAlignmentBytes]; + Q_memmove( pRegistersNew, pRegistersCur, sizeof(jmp_buf) ); + pRegistersCur = pRegistersNew; + } + + return *reinterpret_cast( pRegistersCur ); +#else + return m_Registers; +#endif + } + + ~CCoroutine() + { + if ( m_pSavedStack ) + { + FreePv( m_pSavedStack ); + } + } + + FORCEINLINE void RestoreStack() + { + if ( m_cubSavedStack ) + { + Assert( m_pStackHigh ); + Assert( m_pSavedStack ); + +#if defined( _PS3 ) && defined( _DEBUG ) + // Our (and Sony's) memory tracking tools may try to walk the stack during a free() call + // if we do the free here at our normal point though the stack is invalid since it's in + // the middle of swapping. Instead move it to a temp buffer now and free while the stack + // frames in place are still ok. + Assert( m_cubSavedStack < Q_ARRAYSIZE( rgStackTempBuffer ) ); + memcpy( &rgStackTempBuffer[0], m_pSavedStack, m_cubSavedStack ); + + FreePv( m_pSavedStack ); + m_pSavedStack = &rgStackTempBuffer[0]; +#endif + + // Assert we're not about to trash our own immediate stack + GetStackPtr( pStack ); + if ( pStack >= m_pStackLow && pStack <= m_pStackHigh ) + { + CoroutineDbgMsg( g_fmtstr.sprintf( "Restoring stack over ESP (%x, %x, %x)\n", pStack, m_pStackLow, m_pStackHigh ) ); + AssertMsg3( false, "Restoring stack over ESP (%p, %p, %p)\n", pStack, m_pStackLow, m_pStackHigh ); + } + + // Make sure we can access the our instance pointer after restoring the stack. This function is inlined, so the compiler could decide to + // use an existing coroutine pointer that is already on the stack from the previous function (does so on the PS3), and will be overwritten + // when we memcpy below. Any allocations here should be ok, as the caller should have advanced the stack past the stack area where the + // new stack will be copied + CCoroutine *pThis = (CCoroutine*)stackalloc( sizeof( CCoroutine* ) ); + pThis = this; + + RW_MEMORY_BARRIER; + memcpy( m_pStackLow, m_pSavedStack, m_cubSavedStack ); + + // WARNING: The stack has been replaced.. do not use previous stack variables or this + +#ifdef CHECK_STACK_CORRUPTION + MD5Init( &pThis->m_md52 ); + MD5Update( &pThis->m_md52, pThis->m_pStackLow, pThis->m_cubSavedStack ); + MD5Final( pThis->m_digest2, &pThis->m_md52 ); + Assert( 0 == Q_memcmp( pThis->m_digest, pThis->m_digest2, MD5_DIGEST_LENGTH ) ); + +#endif + + // free the saved stack info + pThis->m_cubSavedStack = 0; +#if !defined( _PS3 ) || !defined( _DEBUG ) + FreePv( pThis->m_pSavedStack ); +#endif + pThis->m_pSavedStack = NULL; + + // If we were the "main thread", reset our stack pos to zero + if ( NULL == pThis->m_pFunc ) + { + pThis->m_pStackLow = pThis->m_pStackHigh = 0; + } + + // resume accounting against the vprof node we were in when we yielded + // Make sure we are added after the coroutine we just copied onto the stack +#if defined( VPROF_ENABLED ) + pThis->m_pVProfNodeScope = g_VProfCurrentProfile.GetCurrentNode(); + + if ( g_VProfCurrentProfile.IsEnabled() ) + { + FOR_EACH_VEC_BACK( pThis->m_vecProfNodeStack, i ) + { + g_VProfCurrentProfile.EnterScope( + pThis->m_vecProfNodeStack[i]->GetName(), + 0, + g_VProfCurrentProfile.GetBudgetGroupName( pThis->m_vecProfNodeStack[i]->GetBudgetGroupID() ), + false, + g_VProfCurrentProfile.GetBudgetGroupFlags( pThis->m_vecProfNodeStack[i]->GetBudgetGroupID() ) + ); + } + } + + pThis->m_vecProfNodeStack.Purge(); +#endif + } + } + + FORCEINLINE void SaveStack() + { + MEM_ALLOC_CREDIT_( "Coroutine saved stack" ); + if ( m_pSavedStack ) + { + FreePv( m_pSavedStack ); + } + + + GetStackPtr( pLocal ); + + m_pStackLow = pLocal; + m_cubSavedStack = (m_pStackHigh - m_pStackLow); + m_pSavedStack = (byte *)PvAlloc( m_cubSavedStack ); + + // if you hit this assert, it's because you're allocating way too much stuff on the stack in your job + // check you haven't got any overly large string buffers allocated on the stack + Assert( m_cubSavedStack < k_cubMaxCoroutineStackSize ); + +#if defined( VPROF_ENABLED ) + // Exit any current vprof scope when we yield, and remember the vprof stack so we can restore it when we run again + m_vecProfNodeStack.RemoveAll(); + + CVProfNode *pCurNode = g_VProfCurrentProfile.GetCurrentNode(); + while ( pCurNode && m_pVProfNodeScope && pCurNode != m_pVProfNodeScope && pCurNode != g_VProfCurrentProfile.GetRoot() ) + { + m_vecProfNodeStack.AddToTail( pCurNode ); + g_VProfCurrentProfile.ExitScope(); + pCurNode = g_VProfCurrentProfile.GetCurrentNode(); + } + + m_pVProfNodeScope = NULL; +#endif + + RW_MEMORY_BARRIER; + // save the stack in the newly allocated slot + memcpy( m_pSavedStack, m_pStackLow, m_cubSavedStack ); + +#ifdef CHECK_STACK_CORRUPTION + MD5Init( &m_md5 ); + MD5Update( &m_md5, m_pSavedStack, m_cubSavedStack ); + MD5Final( m_digest, &m_md5 ); +#endif + } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ) + { + validator.Push( "CCoroutine", this, pchName ); + validator.ClaimMemory( m_pSavedStack ); + validator.Pop(); + } +#endif + +#ifdef _M_X64 + unsigned char m_rgubRegisters[sizeof(jmp_buf) + _REGISTER_ALIGNMENT]; + int m_nAlignmentBytes; +#else + jmp_buf m_Registers; +#endif + + byte *m_pStackHigh; // position of initial entry to the coroutine (stack ptr before continue is ran) + byte *m_pStackLow; // low point on the stack we plan on saving (stack ptr when we yield) + byte *m_pSavedStack; // pointer to the saved stack (allocated on heap) + int m_cubSavedStack; // amount of data on stack + const char *m_pchName; + int m_iJumpCode; + const char *m_pchDebugMsg; + +#ifdef COROUTINE_TRACE + HCoroutine m_hCoroutine; // for debugging +#endif + + CoroutineFunc_t m_pFunc; + void *m_pvParam; +#if defined( VPROF_ENABLED ) + CUtlVector m_vecProfNodeStack; + CVProfNode *m_pVProfNodeScope; +#endif + +#ifdef CHECK_STACK_CORRUPTION + MD5Context_t m_md5; + unsigned char m_digest[MD5_DIGEST_LENGTH]; + MD5Context_t m_md52; + unsigned char m_digest2[MD5_DIGEST_LENGTH]; +#endif +}; + +//----------------------------------------------------------------------------- +// Purpose: manages list of all coroutines +//----------------------------------------------------------------------------- +class CCoroutineMgr +{ +public: + CCoroutineMgr() + { + m_topofexceptionchain = 0; + + // reserve the 0 index as the main coroutine + HCoroutine hMainCoroutine = m_ListCoroutines.AddToTail(); + + m_ListCoroutines[hMainCoroutine].m_pchName = "(main)"; +#ifdef COROUTINE_TRACE + m_ListCoroutines[hMainCoroutine].m_hCoroutine = hMainCoroutine; +#endif + + // mark it as currently running + m_VecCoroutineStack.AddToTail( hMainCoroutine ); + } + + HCoroutine CreateCoroutine( CoroutineFunc_t pFunc, void *pvParam ) + { + HCoroutine hCoroutine = m_ListCoroutines.AddToTail(); + + CoroutineDbgMsg( g_fmtstr.sprintf( "Coroutine_Create() hCoroutine = %x pFunc = 0x%x pvParam = 0x%x\n", hCoroutine, pFunc, pvParam ) ); + + m_ListCoroutines[hCoroutine].m_pFunc = pFunc; + m_ListCoroutines[hCoroutine].m_pvParam = pvParam; + m_ListCoroutines[hCoroutine].m_pSavedStack = NULL; + m_ListCoroutines[hCoroutine].m_cubSavedStack = 0; + m_ListCoroutines[hCoroutine].m_pStackHigh = m_ListCoroutines[hCoroutine].m_pStackLow = NULL; + m_ListCoroutines[hCoroutine].m_pchName = "(no name set)"; +#ifdef COROUTINE_TRACE + m_ListCoroutines[hCoroutine].m_hCoroutine = hCoroutine; +#endif + + return hCoroutine; + } + + HCoroutine GetActiveCoroutineHandle() + { + // look up the coroutine of the last item on the stack + return m_VecCoroutineStack[m_VecCoroutineStack.Count() - 1]; + } + + CCoroutine &GetActiveCoroutine() + { + // look up the coroutine of the last item on the stack + return m_ListCoroutines[GetActiveCoroutineHandle()]; + } + + CCoroutine &GetPreviouslyActiveCoroutine() + { + // look up the coroutine that ran the current coroutine + return m_ListCoroutines[m_VecCoroutineStack[m_VecCoroutineStack.Count() - 2]]; + } + + bool IsValidCoroutine( HCoroutine hCoroutine ) + { + return m_ListCoroutines.IsValidIndex( hCoroutine ) && hCoroutine > 0; + } + + void SetActiveCoroutine( HCoroutine hCoroutine ) + { + m_VecCoroutineStack.AddToTail( hCoroutine ); + } + + void PopCoroutineStack() + { + Assert( m_VecCoroutineStack.Count() > 1 ); + m_VecCoroutineStack.Remove( m_VecCoroutineStack.Count() - 1 ); + } + + bool IsAnyCoroutineActive() + { + return m_VecCoroutineStack.Count() > 1; + } + + void DeleteCoroutine( HCoroutine hCoroutine ) + { + m_ListCoroutines.Remove( hCoroutine ); + } + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, const char *pchName ) + { + validator.Push( "CCoroutineMgr", this, pchName ); + + ValidateObj( m_ListCoroutines ); + FOR_EACH_LL( m_ListCoroutines, iRoutine ) + { + ValidateObj( m_ListCoroutines[iRoutine] ); + } + ValidateObj( m_VecCoroutineStack ); + + validator.Pop(); + } +#endif // DBGFLAG_VALIDATE + + uint32 m_topofexceptionchain; + +private: + CUtlLinkedList m_ListCoroutines; + CUtlVector m_VecCoroutineStack; +}; + +CThreadLocalPtr< CCoroutineMgr > g_ThreadLocalCoroutineMgr; + +CUtlVector< CCoroutineMgr * > g_VecPCoroutineMgr; +CThreadMutex g_ThreadMutexCoroutineMgr; + +CCoroutineMgr &GCoroutineMgr() +{ + if ( !g_ThreadLocalCoroutineMgr ) + { + AUTO_LOCK( g_ThreadMutexCoroutineMgr ); + g_ThreadLocalCoroutineMgr = new CCoroutineMgr(); + g_VecPCoroutineMgr.AddToTail( g_ThreadLocalCoroutineMgr ); + } + + return *g_ThreadLocalCoroutineMgr; +} + + +//----------------------------------------------------------------------------- +// Purpose: call when a thread is quiting to release any per-thread memory +//----------------------------------------------------------------------------- +void Coroutine_ReleaseThreadMemory() +{ + AUTO_LOCK( g_ThreadMutexCoroutineMgr ); + + if ( g_ThreadLocalCoroutineMgr != NULL ) + { + int iCoroutineMgr = g_VecPCoroutineMgr.Find( g_ThreadLocalCoroutineMgr ); + delete g_VecPCoroutineMgr[iCoroutineMgr]; + g_VecPCoroutineMgr.Remove( iCoroutineMgr ); + } +} + + +// predecs +void Coroutine_Launch( CCoroutine &coroutine ); +void Coroutine_Finish(); + + +//----------------------------------------------------------------------------- +// Purpose: Creates a soroutine, specified by the function, returns a handle +//----------------------------------------------------------------------------- +HCoroutine Coroutine_Create( CoroutineFunc_t pFunc, void *pvParam ) +{ + return GCoroutineMgr().CreateCoroutine( pFunc, pvParam ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Continues a current coroutine +// input: hCoroutine - the coroutine to continue +// pchDebugMsg - if non-NULL, it will generate an assertion in +// that coroutine, then that coroutine will +// immediately yield back to this thread +//----------------------------------------------------------------------------- +static const char *k_pchDebugMsg_GenericBreak = (const char *)1; + +bool Internal_Coroutine_Continue( HCoroutine hCoroutine, const char *pchDebugMsg, const char *pchName ) +{ + Assert( GCoroutineMgr().IsValidCoroutine(hCoroutine) ); + + bool bInCoroutineAlready = GCoroutineMgr().IsAnyCoroutineActive(); + +#ifdef _WIN32 +#ifndef _WIN64 + // make sure nobody has a try/catch block and then yielded + // because we hate that and we will crash + uint32 topofexceptionchain; + __asm mov eax, dword ptr fs:[0] + __asm mov topofexceptionchain, eax + if ( GCoroutineMgr().m_topofexceptionchain == 0 ) + GCoroutineMgr().m_topofexceptionchain = topofexceptionchain; + else + { + Assert( topofexceptionchain == GCoroutineMgr().m_topofexceptionchain ); + } +#endif +#endif + + // start the new coroutine + GCoroutineMgr().SetActiveCoroutine( hCoroutine ); + + CCoroutine &coroutinePrev = GCoroutineMgr().GetPreviouslyActiveCoroutine(); + CCoroutine &coroutine = GCoroutineMgr().GetActiveCoroutine(); + if ( pchName ) + coroutine.m_pchName = pchName; + + CoroutineDbgMsg( g_fmtstr.sprintf( "Coroutine_Continue() %s#%x -> %s#%x\n", coroutinePrev.m_pchName, coroutinePrev.m_hCoroutine, coroutine.m_pchName, coroutine.m_hCoroutine ) ); + + bool bStillRunning = true; + + // set the point for the coroutine to jump back to + RW_MEMORY_BARRIER; + int iResult = setjmp( coroutinePrev.GetRegisters() ); + if ( iResult == k_iSetJmpStateSaved ) + { + // copy the new stack in place + if ( coroutine.m_pSavedStack ) + { + // save any of the main stack that overlaps where the coroutine stack is going to go + GetStackPtr( pStackSavePoint ); + if ( pStackSavePoint <= coroutine.m_pStackHigh ) + { + // save the main stack from where the coroutine stack wishes to start + // if the previous coroutine already had a stack save point, just save + // the whole thing. + if ( NULL == coroutinePrev.m_pStackHigh ) + { + coroutinePrev.m_pStackHigh = coroutine.m_pStackHigh; + } + else + { + Assert( coroutine.m_pStackHigh <= coroutinePrev.m_pStackHigh ); + } + coroutinePrev.SaveStack(); + CoroutineDbgMsg( g_fmtstr.sprintf( "SaveStack() %s#%x [%x - %x]\n", coroutinePrev.m_pchName, coroutinePrev.m_hCoroutine, coroutinePrev.m_pStackLow, coroutinePrev.m_pStackHigh ) ); + } + + // If the coroutine's stack is close enough to where we are on the stack, we need to push ourselves + // down past it, so that the memcpy() doesn't screw up the RestoreStack->memcpy call chain. + if ( coroutine.m_pStackHigh > ( pStackSavePoint - 2048 ) ) + { + // If the entire CR stack is above us, we don't need to pad ourselves. + if ( coroutine.m_pStackLow < pStackSavePoint ) + { + // push ourselves down + int cubPush = pStackSavePoint - coroutine.m_pStackLow + 512; + volatile byte *pvStackGap = (byte*)stackalloc( cubPush ); + pvStackGap[ cubPush-1 ] = 0xF; + CoroutineDbgMsg( g_fmtstr.sprintf( "Adjusting stack point by %d (%x <- %x)\n", cubPush, pvStackGap, &pvStackGap[cubPush] ) ); + } + } + + // This needs to go right here - after we've maybe padded the stack (so that iJumpCode does not + // get stepped on) and before the RestoreStack() call (because that might step on pchDebugMsg!). + if ( pchDebugMsg == NULL ) + { + coroutine.m_iJumpCode = k_iSetJmpContinue; + coroutine.m_pchDebugMsg = NULL; + } + else if ( pchDebugMsg == k_pchDebugMsg_GenericBreak ) + { + coroutine.m_iJumpCode = k_iSetJmpDbgBreak; + coroutine.m_pchDebugMsg = NULL; + } + else + { + coroutine.m_iJumpCode = k_iSetJmpDbgBreak; + coroutine.m_pchDebugMsg = pchDebugMsg; + } + + // restore the coroutine stack + CoroutineDbgMsg( g_fmtstr.sprintf( "RestoreStack() %s#%x [%x - %x] (current %x)\n", coroutine.m_pchName, coroutine.m_hCoroutine, coroutine.m_pStackLow, coroutine.m_pStackHigh, pStackSavePoint ) ); + coroutine.RestoreStack(); + + // the new stack is in place, so no code here can reference local stack vars + // move the program counter + RW_MEMORY_BARRIER; + Coroutine_longjmp( GCoroutineMgr().GetActiveCoroutine().GetRegisters(), GCoroutineMgr().GetActiveCoroutine().m_iJumpCode ); + } + else + { + + // set the stack pos for the new coroutine + // jump a long way forward on the stack + // this needs to be a stackalloc() instead of a static buffer, so it won't get optimized out in release build + int cubGap = bInCoroutineAlready ? k_cubCoroutineStackGapSmall : k_cubCoroutineStackGap; + volatile byte *pvStackGap = (byte*)stackalloc( cubGap ); + pvStackGap[ cubGap-1 ] = 0xF; + + // hasn't started yet, so launch + Coroutine_Launch( coroutine ); + } + + // when the job yields, the above setjmp() will be called again with non-zero value + // code here will never run + } + else if ( iResult == k_iSetJmpContinue ) + { + // just pass through + } + else if ( iResult == k_iSetJmpDone ) + { + // we're done, remove the coroutine + GCoroutineMgr().DeleteCoroutine( Coroutine_GetCurrentlyActive() ); + bStillRunning = false; + } + + // job has suspended itself, we'll get back to it later + GCoroutineMgr().PopCoroutineStack(); + return bStillRunning; +} + + +//----------------------------------------------------------------------------- +// Purpose: Continues a current coroutine +//----------------------------------------------------------------------------- +bool Coroutine_Continue( HCoroutine hCoroutine, const char *pchName ) +{ + return Internal_Coroutine_Continue( hCoroutine, NULL, pchName ); +} + + +//----------------------------------------------------------------------------- +// Purpose: launches a coroutine way ahead on the stack +//----------------------------------------------------------------------------- +void NOINLINE Coroutine_Launch( CCoroutine &coroutine ) +{ +#if defined( VPROF_ENABLED ) + coroutine.m_pVProfNodeScope = g_VProfCurrentProfile.GetCurrentNode(); +#endif + + // set our marker +#ifndef _PS3 + GetStackPtr( pEsp ); +#else + // The stack pointer for the current stack frame points to the top of the stack which already includes space for the + // ABI linkage area. We need to include this area as part of our coroutine stack, as the calling function will copy + // the link register (return address to this function) into this area after calling m_pFunc below. Failing to do so + // could result in the coroutine to return to garbage when complete + uint64 *pStackFrameTwoUp = (uint64*)__builtin_frame_address(2); + + // Need to terminate the stack frame sequence so if someone tries to walk the stack in a co-routine they don't go forever. + *pStackFrameTwoUp = 0; + + // Need to track where we we save up to on yield, add a few bytes so we save just the beginning linkage area of the stack frame + // we added the null termination to. + byte * pEsp = ((byte*)pStackFrameTwoUp)+32; + +#endif + #ifdef _WIN64 + // Add a little extra padding, to capture the spill space for the registers + // that is required for us to reserve ABOVE the return address), and also + // align the stack + coroutine.m_pStackHigh = (byte *)( ((uintptr_t)pEsp + 32 + 15) & ~(uintptr_t)15 ); + + // On Win64, we need to be able to find an exception handler + // if we walk the stack to this point. Currently, + // this is as close to the root as we can go. If we + // try to go higher, we wil fail. That's actually + // OK at run time, because Coroutine_Finish doesn't + // return! + CatchAndWriteMiniDumpForVoidPtrFn( coroutine.m_pFunc, coroutine.m_pvParam, /*bExitQuietly*/ true ); + #else + coroutine.m_pStackHigh = (byte *)pEsp; + + // run the function directly + coroutine.m_pFunc( coroutine.m_pvParam ); + #endif + + // longjmp back to the main 'thread' + Coroutine_Finish(); +} + + +//----------------------------------------------------------------------------- +// Purpose: cancels a currently running coroutine +//----------------------------------------------------------------------------- +void Coroutine_Cancel( HCoroutine hCoroutine ) +{ + GCoroutineMgr().DeleteCoroutine( hCoroutine ); +} + +//----------------------------------------------------------------------------- +// Purpose: cause a debug break in the specified coroutine +//----------------------------------------------------------------------------- +void Coroutine_DebugBreak( HCoroutine hCoroutine ) +{ + Internal_Coroutine_Continue( hCoroutine, k_pchDebugMsg_GenericBreak, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: generate an assert (perhaps generating a minidump), with the +// specified failure message, in the specified coroutine +//----------------------------------------------------------------------------- +void Coroutine_DebugAssert( HCoroutine hCoroutine, const char *pchMsg ) +{ + Assert( pchMsg ); + Internal_Coroutine_Continue( hCoroutine, pchMsg, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the code is currently running inside of a coroutine +//----------------------------------------------------------------------------- +bool Coroutine_IsActive() +{ + return GCoroutineMgr().IsAnyCoroutineActive(); +} + + +//----------------------------------------------------------------------------- +// Purpose: returns a handle the currently active coroutine +//----------------------------------------------------------------------------- +HCoroutine Coroutine_GetCurrentlyActive() +{ + Assert( Coroutine_IsActive() ); + return GCoroutineMgr().GetActiveCoroutineHandle(); +} + + +//----------------------------------------------------------------------------- +// Purpose: lets the main thread continue +//----------------------------------------------------------------------------- +void Coroutine_YieldToMain() +{ + // if you've hit this assert, it's because you're calling yield when not in a coroutine + Assert( Coroutine_IsActive() ); + CCoroutine &coroutinePrev = GCoroutineMgr().GetPreviouslyActiveCoroutine(); + CCoroutine &coroutine = GCoroutineMgr().GetActiveCoroutine(); + CoroutineDbgMsg( g_fmtstr.sprintf( "Coroutine_YieldToMain() %s#%x -> %s#%x\n", coroutine.m_pchName, coroutine.m_hCoroutine, coroutinePrev.m_pchName, coroutinePrev.m_hCoroutine ) ); + +#ifdef _WIN32 +#ifndef _WIN64 + // make sure nobody has a try/catch block and then yielded + // because we hate that and we will crash + uint32 topofexceptionchain; + __asm mov eax, dword ptr fs:[0] + __asm mov topofexceptionchain, eax + if ( GCoroutineMgr().m_topofexceptionchain == 0 ) + GCoroutineMgr().m_topofexceptionchain = topofexceptionchain; + else + { + Assert( topofexceptionchain == GCoroutineMgr().m_topofexceptionchain ); + } +#endif +#endif + + RW_MEMORY_BARRIER; + int iResult = setjmp( coroutine.GetRegisters() ); + if ( ( iResult == k_iSetJmpStateSaved ) || ( iResult == k_iSetJmpDbgBreak ) ) + { + + + // break / assert requested? + if ( iResult == k_iSetJmpDbgBreak ) + { + // Assert (minidump) requested? + if ( coroutine.m_pchDebugMsg ) + { + // Generate a failed assertion + AssertMsg1( !"Coroutine assert requested", "%s", coroutine.m_pchDebugMsg ); + } + else + { + // If we were loaded only to debug, call a break + DebuggerBreakIfDebugging(); + } + + // Now IMMEDIATELY yield back to the main thread + } + + // Clear message, regardless + coroutine.m_pchDebugMsg = NULL; + + // save our stack - all the way to the top, err bottom err, the end of it ( where esp is ) + coroutine.SaveStack(); + CoroutineDbgMsg( g_fmtstr.sprintf( "SaveStack() %s#%x [%x - %x]\n", coroutine.m_pchName, coroutine.m_hCoroutine, coroutine.m_pStackLow, coroutine.m_pStackHigh ) ); + + // restore the main thread stack + // allocate a bunch of stack padding so we don't kill ourselves while in stack restoration + // If the coroutine's stack is close enough to where we are on the stack, we need to push ourselves + // down past it, so that the memcpy() doesn't screw up the RestoreStack->memcpy call chain. + GetStackPtr( pStackPtr ); + if ( pStackPtr >= (coroutinePrev.m_pStackHigh - coroutinePrev.m_cubSavedStack) && ( pStackPtr - 2048 ) <= coroutinePrev.m_pStackHigh ) + { + int cubPush = coroutinePrev.m_cubSavedStack + 512; + volatile byte *pvStackGap = (byte*)stackalloc( cubPush ); + pvStackGap[ cubPush - 1 ] = 0xF; + CoroutineDbgMsg( g_fmtstr.sprintf( "Adjusting stack point by %d (%x <- %x)\n", cubPush, pvStackGap, &pvStackGap[cubPush] ) ); + } + + CoroutineDbgMsg( g_fmtstr.sprintf( "RestoreStack() %s#%x [%x - %x]\n", coroutinePrev.m_pchName, coroutinePrev.m_hCoroutine, coroutinePrev.m_pStackLow, coroutinePrev.m_pStackHigh ) ); + coroutinePrev.RestoreStack(); + + // jump back to the main thread + // Our stack may have been mucked with, can't use local vars anymore! + RW_MEMORY_BARRIER; + Coroutine_longjmp( GCoroutineMgr().GetPreviouslyActiveCoroutine().GetRegisters(), k_iSetJmpContinue ); + + UNREACHABLE(); + } + else + { + // we've been restored, now continue on our merry way + } +} + +//----------------------------------------------------------------------------- +// Purpose: done with the Coroutine, terminate safely +//----------------------------------------------------------------------------- +void Coroutine_Finish() +{ + Assert( Coroutine_IsActive() ); + + CoroutineDbgMsg( g_fmtstr.sprintf( "Coroutine_Finish() %s#%x -> %s#%x\n", GCoroutineMgr().GetActiveCoroutine().m_pchName, GCoroutineMgr().GetActiveCoroutineHandle(), GCoroutineMgr().GetPreviouslyActiveCoroutine().m_pchName, &GCoroutineMgr().GetPreviouslyActiveCoroutine() ) ); + + // allocate a bunch of stack padding so we don't kill ourselves while in stack restoration + volatile byte *pvStackGap = (byte*)stackalloc( GCoroutineMgr().GetPreviouslyActiveCoroutine().m_cubSavedStack + 512 ); + pvStackGap[ GCoroutineMgr().GetPreviouslyActiveCoroutine().m_cubSavedStack + 511 ] = 0xf; + + GCoroutineMgr().GetPreviouslyActiveCoroutine().RestoreStack(); + + RW_MEMORY_BARRIER; + // go back to the main thread, signaling that we're done + Coroutine_longjmp( GCoroutineMgr().GetPreviouslyActiveCoroutine().GetRegisters(), k_iSetJmpDone ); + + UNREACHABLE(); +} + +//----------------------------------------------------------------------------- +// Purpose: Coroutine that spawns another coroutine +//----------------------------------------------------------------------------- +void CoroutineTestFunc( void *pvRelaunch ) +{ + static const char *g_pchTestString = "test string"; + + char rgchT[256]; + Q_strncpy( rgchT, g_pchTestString, sizeof(rgchT) ); + + // yield + Coroutine_YieldToMain(); + + // ensure the string is still valid + DbgVerifyNot( Q_strcmp( rgchT, g_pchTestString ) ); + + if ( !pvRelaunch ) + { + // test launching coroutines inside of coroutines + HCoroutine hCoroutine = Coroutine_Create( &CoroutineTestFunc, (void *)(size_t)0xFFFFFFFF ); + // first pass the coroutines should all still be running + DbgVerify( Coroutine_Continue( hCoroutine, NULL ) ); + // second pass the coroutines should all be finished + DbgVerifyNot( Coroutine_Continue( hCoroutine, NULL ) ); + } +} + + +// test that just spins a few times +void CoroutineTestL2( void * ) +{ + // spin a few times + for ( int i = 0; i < 5; i++ ) + { + Coroutine_YieldToMain(); + } +} + + +// level 1 of a test +void CoroutineTestL1( void *pvecCoroutineL2 ) +{ + CUtlVector &vecCoroutineL2 = *(CUtlVector *)pvecCoroutineL2; + + int i = 20; + + // launch a set of coroutines + for ( i = 0; i < 20; i++ ) + { + HCoroutine hCoroutine = Coroutine_Create( &CoroutineTestL2, NULL ); + vecCoroutineL2.AddToTail( hCoroutine ); + Coroutine_Continue( hCoroutine, NULL ); + + // now yield back to main occasionally + if ( i % 2 == 1 ) + Coroutine_YieldToMain(); + } + + Assert( i == 20 ); +} + + +//----------------------------------------------------------------------------- +// Purpose: runs a self-test of the coroutine system +// it's working if it doesn't crash +//----------------------------------------------------------------------------- +bool Coroutine_Test() +{ + // basic calling of a coroutine + HCoroutine hCoroutine = Coroutine_Create( &CoroutineTestFunc, NULL ); + Coroutine_Continue( hCoroutine, NULL ); + Coroutine_Continue( hCoroutine, NULL ); + + // now test + CUtlVector vecCoroutineL2; + hCoroutine = Coroutine_Create( &CoroutineTestL1, &vecCoroutineL2 ); + Coroutine_Continue( hCoroutine, NULL ); + + // run the sub-coroutines until they're all done + while ( vecCoroutineL2.Count() ) + { + if ( hCoroutine && !Coroutine_Continue( hCoroutine, NULL ) ) + hCoroutine = NULL; + + FOR_EACH_VEC_BACK( vecCoroutineL2, i ) + { + if ( !Coroutine_Continue( vecCoroutineL2[i], NULL ) ) + vecCoroutineL2.Remove( i ); + } + } + + + // new one + hCoroutine = Coroutine_Create( &CoroutineTestFunc, NULL ); + // it has yielded, now continue it's call + { + // pop our stack up so it collides with the coroutine stack position + Coroutine_Continue( hCoroutine, NULL ); + volatile byte *pvAlloca = (byte*)stackalloc( k_cubCoroutineStackGapSmall ); + pvAlloca[ k_cubCoroutineStackGapSmall-1 ] = 0xF; + + Coroutine_Continue( hCoroutine, NULL ); + } + + // now do a whole bunch of them + static const int k_nSimultaneousCoroutines = 10 * 1000; + CUtlVector coroutines; + Assert( coroutines.Base() == NULL ); + for (int i = 0; i < k_nSimultaneousCoroutines; i++) + { + coroutines.AddToTail( Coroutine_Create( &CoroutineTestFunc, NULL ) ); + } + + for (int i = 0; i < coroutines.Count(); i++) + { + // first pass the coroutines should all still be running + DbgVerify( Coroutine_Continue( coroutines[i], NULL ) ); + } + + for (int i = 0; i < coroutines.Count(); i++) + { + // second pass the coroutines should all be finished + DbgVerifyNot( Coroutine_Continue( coroutines[i], NULL ) ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns approximate stack depth of current coroutine. +//----------------------------------------------------------------------------- +size_t Coroutine_GetStackDepth() +{ + // should only get called from a coroutine + Assert( GCoroutineMgr().IsAnyCoroutineActive() ); + if ( !GCoroutineMgr().IsAnyCoroutineActive() ) + return 0; + + GetStackPtr( pLocal ); + CCoroutine &coroutine = GCoroutineMgr().GetActiveCoroutine(); + return ( coroutine.m_pStackHigh - pLocal ); +} + + +//----------------------------------------------------------------------------- +// Purpose: validates memory +//----------------------------------------------------------------------------- +void Coroutine_ValidateGlobals( class CValidator &validator ) +{ +#ifdef DBGFLAG_VALIDATE + AUTO_LOCK( g_ThreadMutexCoroutineMgr ); + + for ( int i = 0; i < g_VecPCoroutineMgr.Count(); i++ ) + { + ValidatePtr( g_VecPCoroutineMgr[i] ); + } + ValidateObj( g_VecPCoroutineMgr ); + +#endif +} diff --git a/vstdlib/coroutine_osx.vpc b/vstdlib/coroutine_osx.vpc new file mode 100644 index 0000000..030b05a --- /dev/null +++ b/vstdlib/coroutine_osx.vpc @@ -0,0 +1,31 @@ +//----------------------------------------------------------------------------- +// COROUTINE_OSX.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." + +$include "$SRCDIR\vpc_scripts\source_lib_base.vpc" + +$Configuration +{ + $Compiler + { + $GCC_ExtraCompilerFlags "-fno-stack-protector" + $PreprocessorDefinitions "$BASE;VSTDLIB_DLL_EXPORT" + } +} + +$Project "coroutine_osx" +{ + $Folder "Source Files" + { + $File "coroutine.cpp" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\vstdlib\coroutine.h" + } +} diff --git a/vstdlib/coroutine_win64.masm b/vstdlib/coroutine_win64.masm new file mode 100644 index 0000000..764ebdd --- /dev/null +++ b/vstdlib/coroutine_win64.masm @@ -0,0 +1,175 @@ +option casemap:none + +.CODE + +; import Coroutine_Finish with its mangled Microsoft Visual C++ name +?Coroutine_Finish@@YAXXZ PROTO + +; extern "C" void SaveNonVolatileRegs( uintptr_t regs[8] ); +; incoming parameter is rcs +SaveNonVolatileRegs PROC FRAME + .endprolog + mov qword ptr[rcx], rbx + mov qword ptr[rcx+8], rbp + mov qword ptr[rcx+16], rsi + mov qword ptr[rcx+24], rdi + mov qword ptr[rcx+32], r12 + mov qword ptr[rcx+40], r13 + mov qword ptr[rcx+48], r14 + mov qword ptr[rcx+56], r15 + ret +SaveNonVolatileRegs ENDP + +; extern "C" void NORETURN Coroutine_Launch_ASM( byte **ppStackHigh, uintptr_t **ppLaunchParentFramePtr, void (*pfnExec)( void* ), void *pvParam ) +; Per Win64 ABI, incoming params are rcx, rdx, r8, r9. initial stack pointer is half-aligned due to return address +Coroutine_Launch_ASM PROC FRAME + ; x64 prolog and prolog description macros: + + ; save caller's nonvolatile registers (pushed in reverse order to match SaveNonVolatileRegs) + ; so that we can slam new values in later to trick the x64 callstack unwind procedure + push r15 + .pushreg r15 + push r14 + .pushreg r14 + push r13 + .pushreg r13 + push r12 + .pushreg r12 + push rdi + .pushreg rdi + push rsi + .pushreg rsi + push rbp + .pushreg rbp + push rbx + .pushreg rbx + + ; stack-allocate Win64 function call shadow space for calls to pfnExec and Coroutine_Finish, + ; plus 8 additional bytes to align the stack frame properly (comes in off by 8) + sub rsp, 28h + .allocstack 28h + + .endprolog + + ; compute top of stack for coroutine: 40 bytes for stack, 64 for saved regs, 8 for return address + ; (we do not bother including the additional unused 32 byte shadow space we own above that) + lea rax, [rsp+70h] + mov qword ptr [rcx], rax + + ; save off the address of our saved regs so that we can memcpy over them later and trick + ; the x64 stack unwind logic into walking up to a different Internal_Coroutine_Continue + lea rax, [rsp+28h] + mov qword ptr [rdx], rax + + ; call pfnExec(pvParam) + mov rcx, r9 + call r8 + + ; call Coroutine_Finish - does not return + call ?Coroutine_Finish@@YAXXZ + +Coroutine_Launch_ASM ENDP + + + + +; Needs to match definition found in setjmp.h +_JUMP_BUFFER STRUCT + m_Frame QWORD ? + m_Rbx QWORD ? + m_Rsp QWORD ? + m_Rbp QWORD ? + m_Rsi QWORD ? + m_Rdi QWORD ? + m_R12 QWORD ? + m_R13 QWORD ? + m_R14 QWORD ? + m_R15 QWORD ? + m_Rip QWORD ? + m_MxCsr DWORD ? + m_FpCsr WORD ? + m_Spare WORD ? + m_Xmm6 XMMWORD ? + m_Xmm7 XMMWORD ? + m_Xmm8 XMMWORD ? + m_Xmm9 XMMWORD ? + m_Xmm10 XMMWORD ? + m_Xmm11 XMMWORD ? + m_Xmm12 XMMWORD ? + m_Xmm13 XMMWORD ? + m_Xmm14 XMMWORD ? + m_Xmm15 XMMWORD ? +_JUMP_BUFFER ENDS + + +;This is the reference asm for __intrinsic_setjmp() in VS2015 +;mov qword ptr [rcx],rdx ; intrinsic call site does "mov rdx,rbp" followed by "add rdx,0FFFFFFFFFFFFFFC0h", looks like a nonstandard abi +;mov qword ptr [rcx+8],rbx +;mov qword ptr [rcx+18h],rbp +;mov qword ptr [rcx+20h],rsi +;mov qword ptr [rcx+28h],rdi +;mov qword ptr [rcx+30h],r12 +;mov qword ptr [rcx+38h],r13 +;mov qword ptr [rcx+40h],r14 +;mov qword ptr [rcx+48h],r15 +;lea r8,[rsp+8] ; rsp set to post-return address +;mov qword ptr [rcx+10h],r8 +;mov r8,qword ptr [rsp] +;mov qword ptr [rcx+50h],r8 +;stmxcsr dword ptr [rcx+58h] +;fnstcw word ptr [rcx+5Ch] +;movdqa xmmword ptr [rcx+60h],xmm6 +;ovdqa xmmword ptr [rcx+70h],xmm7 +;movdqa xmmword ptr [rcx+80h],xmm8 +;movdqa xmmword ptr [rcx+90h],xmm9 +;movdqa xmmword ptr [rcx+0A0h],xmm10 +;movdqa xmmword ptr [rcx+0B0h],xmm11 +;movdqa xmmword ptr [rcx+0C0h],xmm12 +;movdqa xmmword ptr [rcx+0D0h],xmm13 +;movdqa xmmword ptr [rcx+0E0h],xmm14 +;movdqa xmmword ptr [rcx+0F0h],xmm15 +;xor eax,eax +;ret + + +; extern "C" void NORETURN Coroutine_LongJmp_UnChecked( jmp_buf buf, int nResult ) +; Per Win64 ABI, incoming params are rcx, rdx, r8, r9. initial stack pointer is half-aligned due to return address +Coroutine_LongJmp_Unchecked PROC + ;load nResult into result from initial setjmp() + xor rax, rax + mov eax, edx + + ;restore to setjmp() caller state + mov rdx, [rcx]._JUMP_BUFFER.m_Frame ; appears to be an error checking value of (_JUMP_BUFFER.m_Rbp + 0FFFFFFFFFFFFFFC0h) passed non-standardly through rdx to setjmp() + mov rbx, [rcx]._JUMP_BUFFER.m_Rbx + mov rsp, [rcx]._JUMP_BUFFER.m_Rsp + mov rbp, [rcx]._JUMP_BUFFER.m_Rbp + mov rsi, [rcx]._JUMP_BUFFER.m_Rsi + mov rdi, [rcx]._JUMP_BUFFER.m_Rdi + mov r12, [rcx]._JUMP_BUFFER.m_R12 + mov r13, [rcx]._JUMP_BUFFER.m_R13 + mov r14, [rcx]._JUMP_BUFFER.m_R14 + mov r15, [rcx]._JUMP_BUFFER.m_R15 + mov r10, [rcx]._JUMP_BUFFER.m_Rip ; store return address in r10 for return + ldmxcsr [rcx]._JUMP_BUFFER.m_MxCsr + fldcw [rcx]._JUMP_BUFFER.m_FpCsr + ;[rcx]._JUMP_BUFFER.m_Spare + movaps xmm6, [rcx]._JUMP_BUFFER.m_Xmm6 + movaps xmm7, [rcx]._JUMP_BUFFER.m_Xmm7 + movaps xmm8, [rcx]._JUMP_BUFFER.m_Xmm8 + movaps xmm9, [rcx]._JUMP_BUFFER.m_Xmm9 + movaps xmm10, [rcx]._JUMP_BUFFER.m_Xmm10 + movaps xmm11, [rcx]._JUMP_BUFFER.m_Xmm11 + movaps xmm12, [rcx]._JUMP_BUFFER.m_Xmm12 + movaps xmm13, [rcx]._JUMP_BUFFER.m_Xmm13 + movaps xmm14, [rcx]._JUMP_BUFFER.m_Xmm14 + movaps xmm15, [rcx]._JUMP_BUFFER.m_Xmm15 + + ;jmp instead of ret to _JUMP_BUFFER.m_Rip because setjmp() already set the _JUMP_BUFFER.m_Rsp to the post-return state + db 048h ; emit a REX prefix on the jmp to ensure it's a full qword + jmp qword ptr r10 +Coroutine_LongJmp_Unchecked ENDP + + +_TEXT ENDS +END diff --git a/vstdlib/cvar.cpp b/vstdlib/cvar.cpp new file mode 100644 index 0000000..72aca7b --- /dev/null +++ b/vstdlib/cvar.cpp @@ -0,0 +1,899 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include "vstdlib/cvar.h" +#include +#include "tier0/icommandline.h" +#include "tier1/utlrbtree.h" +#include "tier1/strtools.h" +#include "tier1/KeyValues.h" +#include "tier1/convar.h" +#include "tier0/vprof.h" +#include "tier1/tier1.h" +#include "tier1/utlbuffer.h" + +#ifdef _X360 +#include "xbox/xbox_console.h" +#endif + +#ifdef POSIX +#include +#include +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +//----------------------------------------------------------------------------- +// Default implementation of CvarQuery +//----------------------------------------------------------------------------- +class CDefaultCvarQuery : public CBaseAppSystem< ICvarQuery > +{ +public: + virtual void *QueryInterface( const char *pInterfaceName ) + { + if ( !Q_stricmp( pInterfaceName, CVAR_QUERY_INTERFACE_VERSION ) ) + return (ICvarQuery*)this; + return NULL; + + } + + virtual bool AreConVarsLinkable( const ConVar *child, const ConVar *parent ) + { + return true; + } +}; + +static CDefaultCvarQuery s_DefaultCvarQuery; +static ICvarQuery *s_pCVarQuery = NULL; + + +//----------------------------------------------------------------------------- +// Default implementation +//----------------------------------------------------------------------------- +class CCvar : public ICvar +{ +public: + CCvar(); + + // Methods of IAppSystem + virtual bool Connect( CreateInterfaceFn factory ); + virtual void Disconnect(); + virtual void *QueryInterface( const char *pInterfaceName ); + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // Inherited from ICVar + virtual CVarDLLIdentifier_t AllocateDLLIdentifier(); + virtual void RegisterConCommand( ConCommandBase *pCommandBase ); + virtual void UnregisterConCommand( ConCommandBase *pCommandBase ); + virtual void UnregisterConCommands( CVarDLLIdentifier_t id ); + virtual const char* GetCommandLineValue( const char *pVariableName ); + virtual ConCommandBase *FindCommandBase( const char *name ); + virtual const ConCommandBase *FindCommandBase( const char *name ) const; + virtual ConVar *FindVar ( const char *var_name ); + virtual const ConVar *FindVar ( const char *var_name ) const; + virtual ConCommand *FindCommand( const char *name ); + virtual const ConCommand *FindCommand( const char *name ) const; + virtual ConCommandBase *GetCommands( void ); + virtual const ConCommandBase *GetCommands( void ) const; + virtual void InstallGlobalChangeCallback( FnChangeCallback_t callback ); + virtual void RemoveGlobalChangeCallback( FnChangeCallback_t callback ); + virtual void CallGlobalChangeCallbacks( ConVar *var, const char *pOldString, float flOldValue ); + virtual void InstallConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc ); + virtual void RemoveConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc ); + virtual void ConsoleColorPrintf( const Color& clr, const char *pFormat, ... ) const; + virtual void ConsolePrintf( const char *pFormat, ... ) const; + virtual void ConsoleDPrintf( const char *pFormat, ... ) const; + virtual void RevertFlaggedConVars( int nFlag ); + virtual void InstallCVarQuery( ICvarQuery *pQuery ); + +#if defined( _X360 ) + virtual void PublishToVXConsole( ); +#endif + + virtual bool IsMaterialThreadSetAllowed( ) const; + virtual void QueueMaterialThreadSetValue( ConVar *pConVar, const char *pValue ); + virtual void QueueMaterialThreadSetValue( ConVar *pConVar, int nValue ); + virtual void QueueMaterialThreadSetValue( ConVar *pConVar, float flValue ); + virtual bool HasQueuedMaterialThreadConVarSets() const; + virtual int ProcessQueuedMaterialThreadConVarSets(); +private: + enum + { + CONSOLE_COLOR_PRINT = 0, + CONSOLE_PRINT, + CONSOLE_DPRINT, + }; + + void DisplayQueuedMessages( ); + + CUtlVector< FnChangeCallback_t > m_GlobalChangeCallbacks; + CUtlVector< IConsoleDisplayFunc* > m_DisplayFuncs; + int m_nNextDLLIdentifier; + ConCommandBase *m_pConCommandList; + + // temporary console area so we can store prints before console display funs are installed + mutable CUtlBuffer m_TempConsoleBuffer; +protected: + + // internals for ICVarIterator + class CCVarIteratorInternal : public ICVarIteratorInternal + { + public: + CCVarIteratorInternal( CCvar *outer ) + : m_pOuter( outer ) + //, m_pHash( &outer->m_CommandHash ), // remember my CCvar, + //m_hashIter( -1, -1 ) // and invalid iterator + , m_pCur( NULL ) + {} + virtual void SetFirst( void ); + virtual void Next( void ); + virtual bool IsValid( void ); + virtual ConCommandBase *Get( void ); + protected: + CCvar * const m_pOuter; + //CConCommandHash * const m_pHash; + //CConCommandHash::CCommandHashIterator_t m_hashIter; + ConCommandBase *m_pCur; + }; + + virtual ICVarIteratorInternal *FactoryInternalIterator( void ); + friend class CCVarIteratorInternal; + + enum ConVarSetType_t + { + CONVAR_SET_STRING = 0, + CONVAR_SET_INT, + CONVAR_SET_FLOAT, + }; + struct QueuedConVarSet_t + { + ConVar *m_pConVar; + ConVarSetType_t m_nType; + int m_nInt; + float m_flFloat; + CUtlString m_String; + }; + CUtlVector< QueuedConVarSet_t > m_QueuedConVarSets; + bool m_bMaterialSystemThreadSetAllowed; + +private: + // Standard console commands -- DO NOT PLACE ANY HIGHER THAN HERE BECAUSE THESE MUST BE THE FIRST TO DESTRUCT + CON_COMMAND_MEMBER_F( CCvar, "find", Find, "Find concommands with the specified string in their name/help text.", 0 ) +}; + +void CCvar::CCVarIteratorInternal::SetFirst( void ) RESTRICT +{ + //m_hashIter = m_pHash->First(); + m_pCur = m_pOuter->GetCommands(); +} + +void CCvar::CCVarIteratorInternal::Next( void ) RESTRICT +{ + //m_hashIter = m_pHash->Next( m_hashIter ); + if ( m_pCur ) + m_pCur = m_pCur->GetNext(); +} + +bool CCvar::CCVarIteratorInternal::IsValid( void ) RESTRICT +{ + //return m_pHash->IsValidIterator( m_hashIter ); + return m_pCur != NULL; +} + +ConCommandBase *CCvar::CCVarIteratorInternal::Get( void ) RESTRICT +{ + Assert( IsValid( ) ); + //return (*m_pHash)[m_hashIter]; + return m_pCur; +} + +ICvar::ICVarIteratorInternal *CCvar::FactoryInternalIterator( void ) +{ + return new CCVarIteratorInternal( this ); +} + +//----------------------------------------------------------------------------- +// Factor for CVars +//----------------------------------------------------------------------------- +static CCvar s_Cvar; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CCvar, ICvar, CVAR_INTERFACE_VERSION, s_Cvar ); + + +//----------------------------------------------------------------------------- +// Returns a CVar dictionary for tool usage +//----------------------------------------------------------------------------- +CreateInterfaceFn VStdLib_GetICVarFactory() +{ + return Sys_GetFactoryThis(); +} + + +//----------------------------------------------------------------------------- +// Constructor +//----------------------------------------------------------------------------- +CCvar::CCvar() : m_TempConsoleBuffer( 0, 1024 ) +{ + m_nNextDLLIdentifier = 0; + m_pConCommandList = NULL; + + m_bMaterialSystemThreadSetAllowed = false; +} + + +//----------------------------------------------------------------------------- +// Methods of IAppSystem +//----------------------------------------------------------------------------- +bool CCvar::Connect( CreateInterfaceFn factory ) +{ + ConnectTier1Libraries( &factory, 1 ); + + s_pCVarQuery = (ICvarQuery*)factory( CVAR_QUERY_INTERFACE_VERSION, NULL ); + if ( !s_pCVarQuery ) + { + s_pCVarQuery = &s_DefaultCvarQuery; + } + + ConVar_Register(); + return true; +} + +void CCvar::Disconnect() +{ + ConVar_Unregister(); + s_pCVarQuery = NULL; + DisconnectTier1Libraries(); +} + +InitReturnVal_t CCvar::Init() +{ + return INIT_OK; +} + +void CCvar::Shutdown() +{ +} + +void *CCvar::QueryInterface( const char *pInterfaceName ) +{ + // We implement the ICvar interface + if ( !V_strcmp( pInterfaceName, CVAR_INTERFACE_VERSION ) ) + return (ICvar*)this; + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Method allowing the engine ICvarQuery interface to take over +//----------------------------------------------------------------------------- +void CCvar::InstallCVarQuery( ICvarQuery *pQuery ) +{ + Assert( s_pCVarQuery == &s_DefaultCvarQuery ); + s_pCVarQuery = pQuery ? pQuery : &s_DefaultCvarQuery; +} + + +//----------------------------------------------------------------------------- +// Used by DLLs to be able to unregister all their commands + convars +//----------------------------------------------------------------------------- +CVarDLLIdentifier_t CCvar::AllocateDLLIdentifier() +{ + return m_nNextDLLIdentifier++; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *variable - +//----------------------------------------------------------------------------- +void CCvar::RegisterConCommand( ConCommandBase *variable ) +{ + // Already registered + if ( variable->IsRegistered() ) + return; + + variable->m_bRegistered = true; + + const char *pName = variable->GetName(); + if ( !pName || !pName[0] ) + { + variable->m_pNext = NULL; + return; + } + + // If the variable is already defined, then setup the new variable as a proxy to it. + const ConCommandBase *pOther = FindVar( variable->GetName() ); + if ( pOther ) + { + if ( variable->IsCommand() || pOther->IsCommand() ) + { + Warning( "WARNING: unable to link %s and %s because one or more is a ConCommand.\n", variable->GetName(), pOther->GetName() ); + } + else + { + // This cast is ok because we make sure they're ConVars above. + const ConVar *pChildVar = static_cast< const ConVar* >( variable ); + const ConVar *pParentVar = static_cast< const ConVar* >( pOther ); + + // See if it's a valid linkage + if ( s_pCVarQuery->AreConVarsLinkable( pChildVar, pParentVar ) ) + { + // Make sure the default values are the same (but only spew about this for FCVAR_REPLICATED) + if( pChildVar->m_pszDefaultValue && pParentVar->m_pszDefaultValue && + pChildVar->IsFlagSet( FCVAR_REPLICATED ) && pParentVar->IsFlagSet( FCVAR_REPLICATED ) ) + { + if( Q_stricmp( pChildVar->m_pszDefaultValue, pParentVar->m_pszDefaultValue ) != 0 ) + { + Warning( "Parent and child ConVars with different default values! %s child: %s parent: %s (parent wins)\n", + variable->GetName(), pChildVar->m_pszDefaultValue, pParentVar->m_pszDefaultValue ); + } + } + + const_cast( pChildVar )->m_pParent = const_cast( pParentVar )->m_pParent; + + // Absorb material thread related convar flags + const_cast( pParentVar )->m_nFlags |= pChildVar->m_nFlags & ( FCVAR_MATERIAL_THREAD_MASK | FCVAR_ACCESSIBLE_FROM_THREADS ); + + // check the parent's callbacks and slam if doesn't have, warn if both have callbacks + if( pChildVar->m_fnChangeCallback ) + { + if ( !pParentVar->m_fnChangeCallback ) + { + const_cast( pParentVar )->m_fnChangeCallback = pChildVar->m_fnChangeCallback; + } + else + { + Warning( "Convar %s has multiple different change callbacks\n", variable->GetName() ); + } + } + + // make sure we don't have conflicting help strings. + if ( pChildVar->m_pszHelpString && Q_strlen( pChildVar->m_pszHelpString ) != 0 ) + { + if ( pParentVar->m_pszHelpString && Q_strlen( pParentVar->m_pszHelpString ) != 0 ) + { + if ( Q_stricmp( pParentVar->m_pszHelpString, pChildVar->m_pszHelpString ) != 0 ) + { + Warning( "Convar %s has multiple help strings:\n\tparent (wins): \"%s\"\n\tchild: \"%s\"\n", + variable->GetName(), pParentVar->m_pszHelpString, pChildVar->m_pszHelpString ); + } + } + else + { + const_cast( pParentVar )->m_pszHelpString = pChildVar->m_pszHelpString; + } + } + + // make sure we don't have conflicting FCVAR_CHEAT flags. + if ( ( pChildVar->m_nFlags & FCVAR_CHEAT ) != ( pParentVar->m_nFlags & FCVAR_CHEAT ) ) + { + Warning( "Convar %s has conflicting FCVAR_CHEAT flags (child: %s, parent: %s, parent wins)\n", + variable->GetName(), ( pChildVar->m_nFlags & FCVAR_CHEAT ) ? "FCVAR_CHEAT" : "no FCVAR_CHEAT", + ( pParentVar->m_nFlags & FCVAR_CHEAT ) ? "FCVAR_CHEAT" : "no FCVAR_CHEAT" ); + } + + // make sure we don't have conflicting FCVAR_REPLICATED flags. + if ( ( pChildVar->m_nFlags & FCVAR_REPLICATED ) != ( pParentVar->m_nFlags & FCVAR_REPLICATED ) ) + { + Warning( "Convar %s has conflicting FCVAR_REPLICATED flags (child: %s, parent: %s, parent wins)\n", + variable->GetName(), ( pChildVar->m_nFlags & FCVAR_REPLICATED ) ? "FCVAR_REPLICATED" : "no FCVAR_REPLICATED", + ( pParentVar->m_nFlags & FCVAR_REPLICATED ) ? "FCVAR_REPLICATED" : "no FCVAR_REPLICATED" ); + } + + // make sure we don't have conflicting FCVAR_DONTRECORD flags. + if ( ( pChildVar->m_nFlags & FCVAR_DONTRECORD ) != ( pParentVar->m_nFlags & FCVAR_DONTRECORD ) ) + { + Warning( "Convar %s has conflicting FCVAR_DONTRECORD flags (child: %s, parent: %s, parent wins)\n", + variable->GetName(), ( pChildVar->m_nFlags & FCVAR_DONTRECORD ) ? "FCVAR_DONTRECORD" : "no FCVAR_DONTRECORD", + ( pParentVar->m_nFlags & FCVAR_DONTRECORD ) ? "FCVAR_DONTRECORD" : "no FCVAR_DONTRECORD" ); + } + } + } + + variable->m_pNext = NULL; + return; + } + + // link the variable in + variable->m_pNext = m_pConCommandList; + m_pConCommandList = variable; +} + +void CCvar::UnregisterConCommand( ConCommandBase *pCommandToRemove ) +{ + // Not registered? Don't bother + if ( !pCommandToRemove->IsRegistered() ) + return; + + pCommandToRemove->m_bRegistered = false; + + // FIXME: Should we make this a doubly-linked list? Would remove faster + ConCommandBase *pPrev = NULL; + for( ConCommandBase *pCommand = m_pConCommandList; pCommand; pCommand = pCommand->m_pNext ) + { + if ( pCommand != pCommandToRemove ) + { + pPrev = pCommand; + continue; + } + + if ( pPrev == NULL ) + { + m_pConCommandList = pCommand->m_pNext; + } + else + { + pPrev->m_pNext = pCommand->m_pNext; + } + pCommand->m_pNext = NULL; + break; + } +} + +// Crash here in TF2, so I'm adding some debugging stuff. +#ifdef WIN32 +#pragma optimize( "", off ) +#endif +void CCvar::UnregisterConCommands( CVarDLLIdentifier_t id ) +{ + ConCommandBase *pNewList; + ConCommandBase *pCommand, *pNext; + + int iCommandsLooped = 0; + + pNewList = NULL; + pCommand = m_pConCommandList; + while ( pCommand ) + { + pNext = pCommand->m_pNext; + if ( pCommand->GetDLLIdentifier() != id ) + { + pCommand->m_pNext = pNewList; + pNewList = pCommand; + } + else + { + // Unlink + pCommand->m_bRegistered = false; + pCommand->m_pNext = NULL; + } + + pCommand = pNext; + iCommandsLooped++; + } + + m_pConCommandList = pNewList; +} +#ifdef WIN32 +#pragma optimize( "", on ) +#endif + + +//----------------------------------------------------------------------------- +// Finds base commands +//----------------------------------------------------------------------------- +const ConCommandBase *CCvar::FindCommandBase( const char *name ) const +{ + const ConCommandBase *cmd = GetCommands(); + for ( ; cmd; cmd = cmd->GetNext() ) + { + if ( !Q_stricmp( name, cmd->GetName() ) ) + return cmd; + } + return NULL; +} + +ConCommandBase *CCvar::FindCommandBase( const char *name ) +{ + ConCommandBase *cmd = GetCommands(); + for ( ; cmd; cmd = cmd->GetNext() ) + { + if ( !Q_stricmp( name, cmd->GetName() ) ) + return cmd; + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose Finds ConVars +//----------------------------------------------------------------------------- +const ConVar *CCvar::FindVar( const char *var_name ) const +{ + VPROF_INCREMENT_COUNTER( "CCvar::FindVar", 1 ); + VPROF( "CCvar::FindVar" ); + const ConCommandBase *var = FindCommandBase( var_name ); + if ( !var || var->IsCommand() ) + return NULL; + + return static_cast(var); +} + +ConVar *CCvar::FindVar( const char *var_name ) +{ + VPROF_INCREMENT_COUNTER( "CCvar::FindVar", 1 ); + VPROF( "CCvar::FindVar" ); + ConCommandBase *var = FindCommandBase( var_name ); + if ( !var || var->IsCommand() ) + return NULL; + + return static_cast( var ); +} + + +//----------------------------------------------------------------------------- +// Purpose Finds ConCommands +//----------------------------------------------------------------------------- +const ConCommand *CCvar::FindCommand( const char *pCommandName ) const +{ + const ConCommandBase *var = FindCommandBase( pCommandName ); + if ( !var || !var->IsCommand() ) + return NULL; + + return static_cast(var); +} + +ConCommand *CCvar::FindCommand( const char *pCommandName ) +{ + ConCommandBase *var = FindCommandBase( pCommandName ); + if ( !var || !var->IsCommand() ) + return NULL; + + return static_cast( var ); +} + + +const char* CCvar::GetCommandLineValue( const char *pVariableName ) +{ + int nLen = Q_strlen(pVariableName); + char *pSearch = (char*)stackalloc( nLen + 2 ); + pSearch[0] = '+'; + memcpy( &pSearch[1], pVariableName, nLen + 1 ); + return CommandLine()->ParmValue( pSearch ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConCommandBase *CCvar::GetCommands( void ) +{ + return m_pConCommandList; +} + +const ConCommandBase *CCvar::GetCommands( void ) const +{ + return m_pConCommandList; +} + + +//----------------------------------------------------------------------------- +// Install, remove global callbacks +//----------------------------------------------------------------------------- +void CCvar::InstallGlobalChangeCallback( FnChangeCallback_t callback ) +{ + Assert( callback && m_GlobalChangeCallbacks.Find( callback ) < 0 ); + m_GlobalChangeCallbacks.AddToTail( callback ); +} + +void CCvar::RemoveGlobalChangeCallback( FnChangeCallback_t callback ) +{ + Assert( callback ); + m_GlobalChangeCallbacks.FindAndRemove( callback ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CCvar::CallGlobalChangeCallbacks( ConVar *var, const char *pOldString, float flOldValue ) +{ + int nCallbackCount = m_GlobalChangeCallbacks.Count(); + for ( int i = 0; i < nCallbackCount; ++i ) + { + (*m_GlobalChangeCallbacks[i])( var, pOldString, flOldValue ); + } +} + + +//----------------------------------------------------------------------------- +// Sets convars containing the flags to their default value +//----------------------------------------------------------------------------- +void CCvar::RevertFlaggedConVars( int nFlag ) +{ + for (const ConCommandBase *var= GetCommands() ; var ; var=var->GetNext()) + { + if ( var->IsCommand() ) + continue; + + ConVar *pCvar = ( ConVar * )var; + + if ( !pCvar->IsFlagSet( nFlag ) ) + continue; + + // It's == to the default value, don't count + if ( !Q_stricmp( pCvar->GetDefault(), pCvar->GetString() ) ) + continue; + + pCvar->Revert(); + + // DevMsg( "%s = \"%s\" (reverted)\n", cvar->GetName(), cvar->GetString() ); + } +} + + +//----------------------------------------------------------------------------- +// Deal with queued material system convars +//----------------------------------------------------------------------------- +bool CCvar::IsMaterialThreadSetAllowed( ) const +{ + Assert( ThreadInMainThread() ); + return m_bMaterialSystemThreadSetAllowed; +} + +void CCvar::QueueMaterialThreadSetValue( ConVar *pConVar, const char *pValue ) +{ + Assert( ThreadInMainThread() ); + int j = m_QueuedConVarSets.AddToTail(); + m_QueuedConVarSets[j].m_pConVar = pConVar; + m_QueuedConVarSets[j].m_nType = CONVAR_SET_STRING; + m_QueuedConVarSets[j].m_String = pValue; +} + +void CCvar::QueueMaterialThreadSetValue( ConVar *pConVar, int nValue ) +{ + Assert( ThreadInMainThread() ); + int j = m_QueuedConVarSets.AddToTail(); + m_QueuedConVarSets[j].m_pConVar = pConVar; + m_QueuedConVarSets[j].m_nType = CONVAR_SET_INT; + m_QueuedConVarSets[j].m_nInt = nValue; +} + +void CCvar::QueueMaterialThreadSetValue( ConVar *pConVar, float flValue ) +{ + Assert( ThreadInMainThread() ); + int j = m_QueuedConVarSets.AddToTail(); + m_QueuedConVarSets[j].m_pConVar = pConVar; + m_QueuedConVarSets[j].m_nType = CONVAR_SET_FLOAT; + m_QueuedConVarSets[j].m_flFloat = flValue; +} + +bool CCvar::HasQueuedMaterialThreadConVarSets() const +{ + Assert( ThreadInMainThread() ); + return m_QueuedConVarSets.Count() > 0; +} + +int CCvar::ProcessQueuedMaterialThreadConVarSets() +{ + Assert( ThreadInMainThread() ); + m_bMaterialSystemThreadSetAllowed = true; + + int nUpdateFlags = 0; + int nCount = m_QueuedConVarSets.Count(); + for ( int i = 0; i < nCount; ++i ) + { + const QueuedConVarSet_t& set = m_QueuedConVarSets[i]; + switch( set.m_nType ) + { + case CONVAR_SET_FLOAT: + set.m_pConVar->SetValue( set.m_flFloat ); + break; + case CONVAR_SET_INT: + set.m_pConVar->SetValue( set.m_nInt ); + break; + case CONVAR_SET_STRING: + set.m_pConVar->SetValue( set.m_String ); + break; + } + + nUpdateFlags |= set.m_pConVar->GetFlags() & FCVAR_MATERIAL_THREAD_MASK; + } + + m_QueuedConVarSets.RemoveAll(); + m_bMaterialSystemThreadSetAllowed = false; + return nUpdateFlags; +} + + +//----------------------------------------------------------------------------- +// Display queued messages +//----------------------------------------------------------------------------- +void CCvar::DisplayQueuedMessages( ) +{ + // Display any queued up messages + if ( m_TempConsoleBuffer.TellPut() == 0 ) + return; + + Color clr; + int nStringLength; + while( m_TempConsoleBuffer.IsValid() ) + { + int nType = m_TempConsoleBuffer.GetChar(); + if ( nType == CONSOLE_COLOR_PRINT ) + { + clr.SetRawColor( m_TempConsoleBuffer.GetInt() ); + } + nStringLength = m_TempConsoleBuffer.PeekStringLength(); + char* pTemp = (char*)stackalloc( nStringLength + 1 ); + m_TempConsoleBuffer.GetStringManualCharCount( pTemp, nStringLength + 1 ); + + switch( nType ) + { + case CONSOLE_COLOR_PRINT: + ConsoleColorPrintf( clr, pTemp ); + break; + + case CONSOLE_PRINT: + ConsolePrintf( pTemp ); + break; + + case CONSOLE_DPRINT: + ConsoleDPrintf( pTemp ); + break; + } + } + + m_TempConsoleBuffer.Purge(); +} + + +//----------------------------------------------------------------------------- +// Install a console printer +//----------------------------------------------------------------------------- +void CCvar::InstallConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc ) +{ + Assert( m_DisplayFuncs.Find( pDisplayFunc ) < 0 ); + m_DisplayFuncs.AddToTail( pDisplayFunc ); + DisplayQueuedMessages(); +} + +void CCvar::RemoveConsoleDisplayFunc( IConsoleDisplayFunc* pDisplayFunc ) +{ + m_DisplayFuncs.FindAndRemove( pDisplayFunc ); +} + +void CCvar::ConsoleColorPrintf( const Color& clr, const char *pFormat, ... ) const +{ + char temp[ 8192 ]; + va_list argptr; + va_start( argptr, pFormat ); + _vsnprintf( temp, sizeof( temp ) - 1, pFormat, argptr ); + va_end( argptr ); + temp[ sizeof( temp ) - 1 ] = 0; + + int c = m_DisplayFuncs.Count(); + if ( c == 0 ) + { + m_TempConsoleBuffer.PutChar( CONSOLE_COLOR_PRINT ); + m_TempConsoleBuffer.PutInt( clr.GetRawColor() ); + m_TempConsoleBuffer.PutString( temp ); + return; + } + + for ( int i = 0 ; i < c; ++i ) + { + m_DisplayFuncs[ i ]->ColorPrint( clr, temp ); + } +} + +void CCvar::ConsolePrintf( const char *pFormat, ... ) const +{ + char temp[ 8192 ]; + va_list argptr; + va_start( argptr, pFormat ); + _vsnprintf( temp, sizeof( temp ) - 1, pFormat, argptr ); + va_end( argptr ); + temp[ sizeof( temp ) - 1 ] = 0; + + int c = m_DisplayFuncs.Count(); + if ( c == 0 ) + { + m_TempConsoleBuffer.PutChar( CONSOLE_PRINT ); + m_TempConsoleBuffer.PutString( temp ); + return; + } + + for ( int i = 0 ; i < c; ++i ) + { + m_DisplayFuncs[ i ]->Print( temp ); + } +} + +void CCvar::ConsoleDPrintf( const char *pFormat, ... ) const +{ + char temp[ 8192 ]; + va_list argptr; + va_start( argptr, pFormat ); + _vsnprintf( temp, sizeof( temp ) - 1, pFormat, argptr ); + va_end( argptr ); + temp[ sizeof( temp ) - 1 ] = 0; + + int c = m_DisplayFuncs.Count(); + if ( c == 0 ) + { + m_TempConsoleBuffer.PutChar( CONSOLE_DPRINT ); + m_TempConsoleBuffer.PutString( temp ); + return; + } + + for ( int i = 0 ; i < c; ++i ) + { + m_DisplayFuncs[ i ]->DPrint( temp ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#if defined( _X360 ) + +void CCvar::PublishToVXConsole() +{ + const char *commands[4096]; + const char *helptext[4096]; + const ConCommandBase *pCur; + int numCommands = 0; + + // iterate and publish commands to the remote console + for ( pCur = m_pConCommandList; pCur; pCur=pCur->GetNext() ) + { + // add unregistered commands to list + if ( numCommands < sizeof(commands)/sizeof(commands[0]) ) + { + commands[numCommands] = pCur->GetName(); + helptext[numCommands] = pCur->GetHelpText(); + numCommands++; + } + } + + if ( numCommands ) + { + XBX_rAddCommands( numCommands, commands, helptext ); + } +} + +#endif + + +//----------------------------------------------------------------------------- +// Console commands +//----------------------------------------------------------------------------- +void CCvar::Find( const CCommand &args ) +{ + const char *search; + const ConCommandBase *var; + + if ( args.ArgC() != 2 ) + { + ConMsg( "Usage: find \n" ); + return; + } + + // Get substring to find + search = args[1]; + + // Loop through vars and print out findings + for ( var = GetCommands(); var; var=var->GetNext() ) + { + if ( var->IsFlagSet(FCVAR_DEVELOPMENTONLY) || var->IsFlagSet(FCVAR_HIDDEN) ) + continue; + + if ( !Q_stristr( var->GetName(), search ) && + !Q_stristr( var->GetHelpText(), search ) ) + continue; + + ConVar_PrintDescription( var ); + } +} + + diff --git a/vstdlib/getstackptr64.masm b/vstdlib/getstackptr64.masm new file mode 100644 index 0000000..52354b6 --- /dev/null +++ b/vstdlib/getstackptr64.masm @@ -0,0 +1,17 @@ +; call cpuid with args in eax, ecx +; store eax, ebx, ecx, edx to p +PUBLIC GetStackPtr64 +.CODE + +GetStackPtr64 PROC FRAME +; unsigned char* GetStackPtr64(void); + .endprolog + + mov rax, rsp ; get stack ptr + add rax, 8h ; account for 8-byte return value of this function + + ret + +GetStackPtr64 ENDP +_TEXT ENDS +END diff --git a/vstdlib/jobthread.cpp b/vstdlib/jobthread.cpp new file mode 100644 index 0000000..46d843e --- /dev/null +++ b/vstdlib/jobthread.cpp @@ -0,0 +1,1457 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#if defined( _WIN32 ) && !defined( _X360 ) +#define WIN32_LEAN_AND_MEAN +#include +#endif +#include "tier0/dbg.h" +#include "tier0/tslist.h" +#include "tier0/icommandline.h" +#include "vstdlib/jobthread.h" +#include "vstdlib/random.h" +#include "tier1/functors.h" +#include "tier1/fmtstr.h" +#include "tier1/utlvector.h" +#include "tier1/generichash.h" +#include "tier0/vprof.h" + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +#include "tier0/memdbgon.h" + + +class CJobThread; + +//----------------------------------------------------------------------------- + +inline void ServiceJobAndRelease( CJob *pJob, int iThread = -1 ) +{ + // TryLock() would only fail if another thread has entered + // Execute() or Abort() + if ( !pJob->IsFinished() && pJob->TryLock() ) + { + // ...service the request + pJob->SetServiceThread( iThread ); + pJob->Execute(); + pJob->Unlock(); + } + pJob->Release(); +} + +//----------------------------------------------------------------------------- + +class ALIGN16 CJobQueue +{ +public: + CJobQueue() : + m_nItems( 0 ), + m_nMaxItems( INT_MAX ) + { + for ( int i = 0; i < ARRAYSIZE( m_pQueues ); i++ ) + { + m_pQueues[i] = new CTSQueue; + } + } + + ~CJobQueue() + { + for ( int i = 0; i < ARRAYSIZE( m_pQueues ); i++ ) + { + delete m_pQueues[i]; + } + } + + int Count() + { + return m_nItems; + } + + int Count( JobPriority_t priority ) + { + return m_pQueues[priority]->Count(); + } + + + CJob *PrePush() + { + if ( m_nItems >= m_nMaxItems ) + { + CJob *pOverflowJob; + if ( Pop( &pOverflowJob ) ) + { + return pOverflowJob; + } + } + return NULL; + } + + int Push( CJob *pJob, int iThread = -1 ) + { + pJob->AddRef(); + + CJob *pOverflowJob; + int nOverflow = 0; + while ( ( pOverflowJob = PrePush() ) != NULL ) + { + ServiceJobAndRelease( pJob ); + nOverflow++; + } + + m_pQueues[pJob->GetPriority()]->PushItem( pJob ); + + m_mutex.Lock(); + if ( ++m_nItems == 1 ) + { + m_JobAvailableEvent.Set(); + } + m_mutex.Unlock(); + + return nOverflow; + } + + bool Pop( CJob **ppJob ) + { + m_mutex.Lock(); + if ( !m_nItems ) + { + m_mutex.Unlock(); + *ppJob = NULL; + return false; + } + if ( --m_nItems == 0 ) + { + m_JobAvailableEvent.Reset(); + } + m_mutex.Unlock(); + + for ( int i = JP_HIGH; i >= 0; --i ) + { + if ( m_pQueues[i]->PopItem( ppJob ) ) + { + return true; + } + } + + + AssertMsg( 0, "Expected at least one queue item" ); + *ppJob = NULL; + return false; + } + + CThreadEvent &GetEventHandle() + { + return m_JobAvailableEvent; + } + + void Flush() + { + // Only safe to call when system is suspended + m_mutex.Lock(); + m_nItems = 0; + m_JobAvailableEvent.Reset(); + CJob *pJob; + for ( int i = JP_HIGH; i >= 0; --i ) + { + while ( m_pQueues[i]->PopItem( &pJob ) ) + { + pJob->Abort(); + pJob->Release(); + } + } + m_mutex.Unlock(); + } + +private: + CTSQueue *m_pQueues[JP_HIGH + 1]; + int m_nItems; + int m_nMaxItems; + CThreadMutex m_mutex; + CThreadManualEvent m_JobAvailableEvent; + +} ALIGN16_POST; + +//----------------------------------------------------------------------------- +// +// CThreadPool +// +//----------------------------------------------------------------------------- + +class CThreadPool : public CRefCounted1 +{ +public: + CThreadPool(); + ~CThreadPool(); + + //----------------------------------------------------- + // Thread functions + //----------------------------------------------------- + bool Start( const ThreadPoolStartParams_t &startParams = ThreadPoolStartParams_t() ) { return Start( startParams, NULL ); } + bool Start( const ThreadPoolStartParams_t &startParams, const char *pszNameOverride ); + bool Stop( int timeout = TT_INFINITE ); + void Distribute( bool bDistribute = true, int *pAffinityTable = NULL ); + + //----------------------------------------------------- + // Functions for any thread + //----------------------------------------------------- + unsigned GetJobCount() { return m_nJobs; } + int NumThreads(); + int NumIdleThreads(); + + //----------------------------------------------------- + // Pause/resume processing jobs + //----------------------------------------------------- + int SuspendExecution(); + int ResumeExecution(); + + //----------------------------------------------------- + // Offer the current thread to the pool + //----------------------------------------------------- + virtual int YieldWait( CThreadEvent **pEvents, int nEvents, bool bWaitAll = true, unsigned timeout = TT_INFINITE ); + virtual int YieldWait( CJob **, int nJobs, bool bWaitAll = true, unsigned timeout = TT_INFINITE ); + void Yield( unsigned timeout ); + + //----------------------------------------------------- + // Add a native job to the queue (master thread) + //----------------------------------------------------- + void AddJob( CJob * ); + void InsertJobInQueue( CJob * ); + + //----------------------------------------------------- + // All threads execute pFunctor asap. Thread will either wake up + // and execute or execute pFunctor right after completing current job and + // before looking for another job. + //----------------------------------------------------- + void ExecuteHighPriorityFunctor( CFunctor *pFunctor ); + + //----------------------------------------------------- + // Add an function object to the queue (master thread) + //----------------------------------------------------- + void AddFunctorInternal( CFunctor *, CJob ** = NULL, const char *pszDescription = NULL, unsigned flags = 0 ); + + //----------------------------------------------------- + // Remove a job from the queue (master thread) + //----------------------------------------------------- + virtual void ChangePriority( CJob *p, JobPriority_t priority ); + + //----------------------------------------------------- + // Bulk job manipulation (blocking) + //----------------------------------------------------- + int ExecuteToPriority( JobPriority_t toPriority, JobFilter_t pfnFilter = NULL ); + int AbortAll(); + + virtual void Reserved1() {} + + void WaitForIdle( bool bAll = true ); + +private: + enum + { + IO_STACKSIZE = ( 64 * 1024 ), + COMPUTATION_STACKSIZE = 0, + }; + + //----------------------------------------------------- + // + //----------------------------------------------------- + CJob *PeekJob(); + CJob *GetDummyJob(); + + //----------------------------------------------------- + // Thread functions + //----------------------------------------------------- + int Run(); + +private: + friend class CJobThread; + + CJobQueue m_SharedQueue; + CInterlockedInt m_nIdleThreads; + CUtlVector m_Threads; + CUtlVector m_IdleEvents; + + CThreadMutex m_SuspendMutex; + int m_nSuspend; + CInterlockedInt m_nJobs; + + // Some jobs should only be executed on the threadpool thread(s). Ie: the rendering thread has the GL context + // and the main thread coming in and "helping" with jobs breaks that pretty nicely. This flag states that + // only the threadpool threads should execute these jobs. + bool m_bExecOnThreadPoolThreadsOnly; +}; + +//----------------------------------------------------------------------------- + +JOB_INTERFACE IThreadPool *CreateThreadPool() +{ + return new CThreadPool; +} + +JOB_INTERFACE void DestroyThreadPool( IThreadPool *pPool ) +{ + delete pPool; +} + +//----------------------------------------------------------------------------- + +class CGlobalThreadPool : public CThreadPool +{ +public: + virtual bool Start( const ThreadPoolStartParams_t &startParamsIn ) + { + int nThreads = ( CommandLine()->ParmValue( "-threads", -1 ) - 1 ); + ThreadPoolStartParams_t startParams = startParamsIn; + + if ( nThreads >= 0 ) + { + startParams.nThreads = nThreads; + } + else + { + // Cap the GlobPool threads at 4. + startParams.nThreadsMax = 4; + } + return CThreadPool::Start( startParams, "Glob" ); + } + + virtual bool OnFinalRelease() + { + AssertMsg( 0, "Releasing global thread pool object!" ); + return false; + } +}; + +//----------------------------------------------------------------------------- + +class CJobThread : public CWorkerThread +{ +public: + CJobThread( CThreadPool *pOwner, int iThread ) : + m_SharedQueue( pOwner->m_SharedQueue ), + m_pOwner( pOwner ), + m_iThread( iThread ) + { + } + + CThreadEvent &GetIdleEvent() + { + return m_IdleEvent; + } + + CJobQueue &AccessDirectQueue() + { + return m_DirectQueue; + } + +private: + unsigned Wait() + { + unsigned waitResult; + tmZone( TELEMETRY_LEVEL0, TMZF_IDLE, "%s", __FUNCTION__ ); +#ifdef WIN32 + enum Event_t + { + CALL_FROM_MASTER, + SHARED_QUEUE, + DIRECT_QUEUE, + + NUM_EVENTS + }; + + HANDLE waitHandles[NUM_EVENTS]; + + waitHandles[CALL_FROM_MASTER] = GetCallHandle().GetHandle(); + waitHandles[SHARED_QUEUE] = m_SharedQueue.GetEventHandle().GetHandle(); + waitHandles[DIRECT_QUEUE] = m_DirectQueue.GetEventHandle().GetHandle(); + +#ifdef _DEBUG + while ( ( waitResult = WaitForMultipleObjects( ARRAYSIZE(waitHandles), waitHandles, FALSE, 10 ) ) == WAIT_TIMEOUT ) + { + waitResult = waitResult; // break here + } +#else + waitResult = WaitForMultipleObjects( ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE ); +#endif +#else // !win32 + bool bSet = false; + int nWaitTime = 100; + + while( !bSet ) + { + // Jobs are typically enqueued to the shared job queue so wait on it first. + bSet = m_SharedQueue.GetEventHandle().Wait( nWaitTime ); + if( !bSet ) + bSet = m_DirectQueue.GetEventHandle().Wait( 10 ); + if ( !bSet ) + bSet = GetCallHandle().Wait( 0 ); + } + + if ( !bSet ) + waitResult = WAIT_TIMEOUT; + else + waitResult = WAIT_OBJECT_0; +#endif + return waitResult; + } + + int Run() + { + + + // Wait for either a call from the master thread, or an item in the queue... + unsigned waitResult; + bool bExit = false; + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + m_pOwner->m_nIdleThreads++; + m_IdleEvent.Set(); + while (!bExit && ( ( waitResult = Wait() ) != WAIT_FAILED ) ) + { + if ( PeekCall() ) + { + CFunctor *pFunctor = NULL; + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s PeekCall():%d", __FUNCTION__, GetCallParam() ); + + switch ( GetCallParam( &pFunctor ) ) + { + case TPM_EXIT: + Reply( true ); + bExit = TRUE; + break; + + case TPM_SUSPEND: + Reply( true ); + SuspendCooperative(); + break; + + case TPM_RUNFUNCTOR: + if( pFunctor ) + { + ( *pFunctor )(); + Reply( true ); + } + else + { + Assert( pFunctor ); + Reply( false ); + } + break; + + default: + AssertMsg( 0, "Unknown call to thread" ); + Reply( false ); + break; + } + } + else + { + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s !PeekCall()", __FUNCTION__ ); + + CJob *pJob; + bool bTookJob = false; + do + { + if ( !m_DirectQueue.Pop( &pJob) ) + { + if ( !m_SharedQueue.Pop( &pJob ) ) + { + // Nothing to process, return to wait state + break; + } + } + if ( !bTookJob ) + { + m_IdleEvent.Reset(); + m_pOwner->m_nIdleThreads--; + bTookJob = true; + } + ServiceJobAndRelease( pJob, m_iThread ); + m_pOwner->m_nJobs--; + } while ( !PeekCall() ); + + if ( bTookJob ) + { + m_pOwner->m_nIdleThreads++; + m_IdleEvent.Set(); + } + } + } + m_pOwner->m_nIdleThreads--; + m_IdleEvent.Reset(); + return 0; + } + + CJobQueue m_DirectQueue; + CJobQueue & m_SharedQueue; + CThreadPool * m_pOwner; + CThreadManualEvent m_IdleEvent; + int m_iThread; +}; + +//----------------------------------------------------------------------------- + +CGlobalThreadPool g_ThreadPool; +IThreadPool *g_pThreadPool = &g_ThreadPool; + +//----------------------------------------------------------------------------- +// +// CThreadPool +// +//----------------------------------------------------------------------------- + +CThreadPool::CThreadPool() : + m_nIdleThreads( 0 ), + m_nJobs( 0 ), + m_nSuspend( 0 ) +{ +} + +//--------------------------------------------------------- + +CThreadPool::~CThreadPool() +{ + Stop(); +} + +//--------------------------------------------------------- +// +//--------------------------------------------------------- +int CThreadPool::NumThreads() +{ + return m_Threads.Count(); +} + +//--------------------------------------------------------- +// +//--------------------------------------------------------- +int CThreadPool::NumIdleThreads() +{ + return m_nIdleThreads; +} + +void CThreadPool::ExecuteHighPriorityFunctor( CFunctor *pFunctor ) +{ + int i; + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->CallWorker( TPM_RUNFUNCTOR, 0, false, pFunctor ); + } + + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->WaitForReply(); + } +} + +//--------------------------------------------------------- +// Pause/resume processing jobs +//--------------------------------------------------------- +int CThreadPool::SuspendExecution() +{ + AUTO_LOCK( m_SuspendMutex ); + + // If not already suspended + if ( m_nSuspend == 0 ) + { + // Make sure state is correct + int i; + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->CallWorker( TPM_SUSPEND, 0 ); + } + + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->WaitForReply(); + } + + // Because worker must signal before suspending, we could reach + // here with the thread not actually suspended + for ( i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->BWaitForThreadSuspendCooperative(); + } + } + + return m_nSuspend++; +} + +//--------------------------------------------------------- + +int CThreadPool::ResumeExecution() +{ + AUTO_LOCK( m_SuspendMutex ); + AssertMsg( m_nSuspend >= 1, "Attempted resume when not suspended"); + int result = m_nSuspend--; + if (m_nSuspend == 0 ) + { + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->ResumeCooperative(); + } + } + return result; +} + +//--------------------------------------------------------- + +void CThreadPool::WaitForIdle( bool bAll ) +{ + ThreadWaitForEvents( m_IdleEvents.Count(), m_IdleEvents.Base(), bAll, 60000 ); +} + +//--------------------------------------------------------- + +int CThreadPool::YieldWait( CThreadEvent **pEvents, int nEvents, bool bWaitAll, unsigned timeout ) +{ + tmZone( TELEMETRY_LEVEL0, TMZF_IDLE, "%s(%d) SPINNING %t", __FUNCTION__, timeout, tmSendCallStack( TELEMETRY_LEVEL0, 0 ) ); + + Assert( timeout == TT_INFINITE ); // unimplemented + + int result; + CJob *pJob; + // Always wait for zero milliseconds initially, to let us process jobs on this thread. + timeout = 0; + while ( ( result = ThreadWaitForEvents( nEvents, pEvents, bWaitAll, timeout ) ) == WAIT_TIMEOUT ) + { + if ( !m_bExecOnThreadPoolThreadsOnly && m_SharedQueue.Pop( &pJob ) ) + { + ServiceJobAndRelease( pJob ); + m_nJobs--; + } + else + { + // Since there are no jobs for the main thread set the timeout to infinite. + // The only disadvantage to this is that if a job thread creates a new job + // then the main thread will not be available to pick it up, but if that + // is a problem you can just create more worker threads. Debugging test runs + // of TF2 suggests that jobs are only ever added from the main thread which + // means that there is no disadvantage. + // Waiting on the events instead of busy spinning has multiple advantages. + // It avoids wasting CPU time/electricity, it makes it more obvious in profiles + // when the main thread is idle versus busy, and it allows ready thread analysis + // in xperf to find out what woke up a waiting thread. + // It also avoids unnecessary CPU starvation -- seen on customer traces of TF2. + timeout = TT_INFINITE; + } + } + return result; +} + +//--------------------------------------------------------- + +int CThreadPool::YieldWait( CJob **ppJobs, int nJobs, bool bWaitAll, unsigned timeout ) +{ + CUtlVectorFixed handles; + if ( nJobs > handles.NumAllocated() - 2 ) + { + return TW_FAILED; + } + + for ( int i = 0; i < nJobs; i++ ) + { + handles.AddToTail( ppJobs[i]->AccessEvent() ); + } + + return YieldWait( handles.Base(), handles.Count(), bWaitAll, timeout); +} + +//--------------------------------------------------------- + +void CThreadPool::Yield( unsigned timeout ) +{ + // @MULTICORE (toml 10/24/2006): not implemented + Assert( ThreadInMainThread() ); + if ( !ThreadInMainThread() ) + { + ThreadSleep( timeout ); + return; + } + ThreadSleep( timeout ); +} + +//--------------------------------------------------------- +// Add a job to the queue +//--------------------------------------------------------- + +void CThreadPool::AddJob( CJob *pJob ) +{ + if ( !pJob ) + { + return; + } + + if ( pJob->m_ThreadPoolData != JOB_NO_DATA ) + { + Warning( "Cannot add a thread job already committed to another thread pool\n" ); + return; + } + + if ( m_Threads.Count() == 0 ) + { + // So only threadpool jobs are supposed to execute the jobs, but there are no threadpool threads? + Assert( !m_bExecOnThreadPoolThreadsOnly ); + + pJob->Execute(); + return; + } + + int flags = pJob->GetFlags(); + + if ( !m_bExecOnThreadPoolThreadsOnly && ( ( flags & ( JF_IO | JF_QUEUE ) ) == 0 ) /* @TBD && !m_queue.Count() */ ) + { + if ( !NumIdleThreads() ) + { + pJob->Execute(); + return; + } + pJob->SetPriority( JP_HIGH ); + } + + + if ( !pJob->CanExecute() ) + { + // Already handled + ExecuteOnce( Warning( "Attempted to add job to job queue that has already been completed\n" ) ); + return; + } + + pJob->m_pThreadPool = this; + pJob->m_status = JOB_STATUS_PENDING; + InsertJobInQueue( pJob ); + ++m_nJobs; +} + +//--------------------------------------------------------- +// +//--------------------------------------------------------- + +void CThreadPool::InsertJobInQueue( CJob *pJob ) +{ + CJobQueue *pQueue; + + if ( !( pJob->GetFlags() & JF_SERIAL ) ) + { + int iThread = pJob->GetServiceThread(); + if ( iThread == -1 || !m_Threads.IsValidIndex( iThread ) ) + { + pQueue = &m_SharedQueue; + } + else + { + pQueue = &(m_Threads[iThread]->AccessDirectQueue()); + } + } + else + { + pQueue = &(m_Threads[0]->AccessDirectQueue()); + } + + m_nJobs -= pQueue->Push( pJob ); +} + +//--------------------------------------------------------- +// Add an function object to the queue (master thread) +//--------------------------------------------------------- + +void CThreadPool::AddFunctorInternal( CFunctor *pFunctor, CJob **ppJob, const char *pszDescription, unsigned flags ) +{ + // Note: assumes caller has handled refcount + CJob *pJob = new CFunctorJob( pFunctor, pszDescription ); + + pJob->SetFlags( flags ); + + AddJob( pJob ); + + if ( ppJob ) + { + *ppJob = pJob; + } + else + { + pJob->Release(); + } +} + +//--------------------------------------------------------- +// Remove a job from the queue +//--------------------------------------------------------- + +void CThreadPool::ChangePriority( CJob *pJob, JobPriority_t priority ) +{ + // Right now, only support upping the priority + if ( pJob->GetPriority() < priority ) + { + pJob->SetPriority( priority ); + m_SharedQueue.Push( pJob ); + } + else + { + ExecuteOnce( if ( pJob->GetPriority() != priority ) DevMsg( "CThreadPool::RemoveJob not implemented right now" ) ); + } + +} + +//--------------------------------------------------------- +// Execute to a specified priority +//--------------------------------------------------------- + +int CThreadPool::ExecuteToPriority( JobPriority_t iToPriority, JobFilter_t pfnFilter ) +{ + SuspendExecution(); + + CJob *pJob; + int nExecuted = 0; + int i; + int nJobsTotal = GetJobCount(); + CUtlVector jobsToPutBack; + + for ( int iCurPriority = JP_HIGH; iCurPriority >= iToPriority; --iCurPriority ) + { + for ( i = 0; i < m_Threads.Count(); i++ ) + { + CJobQueue &queue = m_Threads[i]->AccessDirectQueue(); + while ( queue.Count( (JobPriority_t)iCurPriority ) ) + { + queue.Pop( &pJob ); + if ( pfnFilter && !(*pfnFilter)( pJob ) ) + { + if ( pJob->CanExecute() ) + { + jobsToPutBack.EnsureCapacity( nJobsTotal ); + jobsToPutBack.AddToTail( pJob ); + } + else + { + m_nJobs--; + pJob->Release(); // an already serviced job in queue, may as well ditch it (as in, main thread probably force executed) + } + continue; + } + ServiceJobAndRelease( pJob ); + m_nJobs--; + nExecuted++; + } + + } + + while ( m_SharedQueue.Count( (JobPriority_t)iCurPriority ) ) + { + m_SharedQueue.Pop( &pJob ); + if ( pfnFilter && !(*pfnFilter)( pJob ) ) + { + if ( pJob->CanExecute() ) + { + jobsToPutBack.EnsureCapacity( nJobsTotal ); + jobsToPutBack.AddToTail( pJob ); + } + else + { + m_nJobs--; + pJob->Release(); // see above + } + continue; + } + + ServiceJobAndRelease( pJob ); + m_nJobs--; + nExecuted++; + } + } + + for ( i = 0; i < jobsToPutBack.Count(); i++ ) + { + InsertJobInQueue( jobsToPutBack[i] ); + jobsToPutBack[i]->Release(); + } + + ResumeExecution(); + + return nExecuted; +} + +//--------------------------------------------------------- +// +//--------------------------------------------------------- + +int CThreadPool::AbortAll() +{ + SuspendExecution(); + CJob *pJob; + + int iAborted = 0; + while ( m_SharedQueue.Pop( &pJob ) ) + { + pJob->Abort(); + pJob->Release(); + iAborted++; + } + + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + CJobQueue &queue = m_Threads[i]->AccessDirectQueue(); + while ( queue.Pop( &pJob ) ) + { + pJob->Abort(); + pJob->Release(); + iAborted++; + } + + } + + m_nJobs = 0; + + ResumeExecution(); + + return iAborted; +} + +//--------------------------------------------------------- +// CThreadPool thread functions +//--------------------------------------------------------- + +bool CThreadPool::Start( const ThreadPoolStartParams_t &startParams, const char *pszName ) +{ + int nThreads = startParams.nThreads; + + m_bExecOnThreadPoolThreadsOnly = startParams.bExecOnThreadPoolThreadsOnly; + + if ( nThreads < 0 ) + { + const CPUInformation &ci = *GetCPUInformation(); + if ( startParams.bIOThreads ) + { + nThreads = ci.m_nLogicalProcessors; + } + else + { + nThreads = ( ci.m_nLogicalProcessors / (( ci.m_bHT ) ? 2 : 1) ) - 1; // One per + if ( IsPC() ) + { + if ( nThreads > 3 ) + { + DevMsg( "Defaulting to limit of 3 worker threads, use -threads on command line if want more\n" ); // Current >4 processor configs don't really work so well, probably due to cache issues? (toml 7/12/2007) + nThreads = 3; + } + } + } + + if ( ( startParams.nThreadsMax >= 0 ) && ( nThreads > startParams.nThreadsMax ) ) + { + nThreads = startParams.nThreadsMax; + } + } + + if ( nThreads <= 0 ) + { + return true; + } + + int nStackSize = startParams.nStackSize; + + if ( nStackSize < 0 ) + { + if ( startParams.bIOThreads ) + { + nStackSize = IO_STACKSIZE; + } + else + { + nStackSize = COMPUTATION_STACKSIZE; + } + } + + int priority = startParams.iThreadPriority; + + if ( priority == SHRT_MIN ) + { + if ( startParams.bIOThreads ) + { + priority = THREAD_PRIORITY_HIGHEST; + } + else + { + priority = ThreadGetPriority(); + } + } + + bool bDistribute; + if ( startParams.fDistribute != TRS_NONE ) + { + bDistribute = ( startParams.fDistribute == TRS_TRUE ); + } + else + { + bDistribute = !startParams.bIOThreads; + } + + //-------------------------------------------------------- + + m_Threads.EnsureCapacity( nThreads ); + m_IdleEvents.EnsureCapacity( nThreads ); + + if ( !pszName ) + { + pszName = ( startParams.bIOThreads ) ? "IOJobX" : "CmpJobX"; + } + while ( nThreads-- ) + { + int iThread = m_Threads.AddToTail(); + m_IdleEvents.AddToTail(); + m_Threads[iThread] = new CJobThread( this, iThread ); + m_IdleEvents[iThread] = &m_Threads[iThread]->GetIdleEvent(); + m_Threads[iThread]->SetName( CFmtStr( "%s%d", pszName, iThread ) ); + m_Threads[iThread]->Start( nStackSize ); + m_Threads[iThread]->GetIdleEvent().Wait(); +#ifdef WIN32 + ThreadSetPriority( (ThreadHandle_t)m_Threads[iThread]->GetThreadHandle(), priority ); +#endif + } + + Distribute( bDistribute, startParams.bUseAffinityTable ? (int *)startParams.iAffinityTable : NULL ); + + return true; +} + +//--------------------------------------------------------- + +void CThreadPool::Distribute( bool bDistribute, int *pAffinityTable ) +{ + if ( bDistribute ) + { + const CPUInformation &ci = *GetCPUInformation(); + int nHwThreadsPer = (( ci.m_bHT ) ? 2 : 1); + if ( ci.m_nLogicalProcessors > 1 ) + { + if ( !pAffinityTable ) + { +#if defined( IS_WINDOWS_PC ) + // no affinity table, distribution is cycled across all available + HINSTANCE hInst = LoadLibrary( "kernel32.dll" ); + if ( hInst ) + { + typedef DWORD (WINAPI *SetThreadIdealProcessorFn)(ThreadHandle_t hThread, DWORD dwIdealProcessor); + SetThreadIdealProcessorFn Thread_SetIdealProcessor = (SetThreadIdealProcessorFn)GetProcAddress( hInst, "SetThreadIdealProcessor" ); + if ( Thread_SetIdealProcessor ) + { + ThreadHandle_t hMainThread = ThreadGetCurrentHandle(); + Thread_SetIdealProcessor( hMainThread, 0 ); + int iProc = 0; + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + iProc += nHwThreadsPer; + if ( iProc >= ci.m_nLogicalProcessors ) + { + iProc %= ci.m_nLogicalProcessors; + if ( nHwThreadsPer > 1 ) + { + iProc = ( iProc + 1 ) % nHwThreadsPer; + } + } + Thread_SetIdealProcessor((ThreadHandle_t)m_Threads[i]->GetThreadHandle(), iProc); + } + } + FreeLibrary( hInst ); + } +#else + // no affinity table, distribution is cycled across all available + int iProc = 0; + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + iProc += nHwThreadsPer; + if ( iProc >= ci.m_nLogicalProcessors ) + { + iProc %= ci.m_nLogicalProcessors; + if ( nHwThreadsPer > 1 ) + { + iProc = ( iProc + 1 ) % nHwThreadsPer; + } + } +#ifdef WIN32 + ThreadSetAffinity( (ThreadHandle_t)m_Threads[i]->GetThreadHandle(), 1 << iProc ); +#endif + } +#endif + } + else + { + // distribution is from affinity table + for ( int i = 0; i < m_Threads.Count(); i++ ) + { +#ifdef WIN32 + ThreadSetAffinity( (ThreadHandle_t)m_Threads[i]->GetThreadHandle(), pAffinityTable[i] ); +#endif + } + } + } + } + else + { +#ifdef WIN32 + DWORD_PTR dwProcessAffinity, dwSystemAffinity; + if ( GetProcessAffinityMask( GetCurrentProcess(), &dwProcessAffinity, &dwSystemAffinity ) ) + { + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + ThreadSetAffinity( (ThreadHandle_t)m_Threads[i]->GetThreadHandle(), dwProcessAffinity ); + } + } +#endif + } +} + +//--------------------------------------------------------- + +bool CThreadPool::Stop( int timeout ) +{ + for ( int i = 0; i < m_Threads.Count(); i++ ) + { + m_Threads[i]->CallWorker( TPM_EXIT ); + } + + for ( int i = 0; i < m_Threads.Count(); ++i ) + { + while( m_Threads[i]->IsAlive() ) + { + ThreadSleep( 0 ); + } + delete m_Threads[i]; + } + + m_nJobs = 0; + m_SharedQueue.Flush(); + m_nIdleThreads = 0; + m_Threads.RemoveAll(); + m_IdleEvents.RemoveAll(); + + return true; +} + +//--------------------------------------------------------- + +CJob *CThreadPool::GetDummyJob() +{ + class CDummyJob : public CJob + { + public: + CDummyJob() + { + Execute(); + } + + virtual JobStatus_t DoExecute() { return JOB_OK; } + }; + + static CDummyJob dummyJob; + + dummyJob.AddRef(); + return &dummyJob; +} + +//----------------------------------------------------------------------------- + + +namespace ThreadPoolTest +{ +int g_iSleep; + +CThreadEvent g_done; +int g_nTotalToComplete; +CThreadPool *g_pTestThreadPool; + +class CCountJob : public CJob +{ +public: + virtual JobStatus_t DoExecute() + { + m_nCount++; + ThreadPause(); + if ( g_iSleep >= 0) + ThreadSleep( g_iSleep ); + if ( bDoWork ) + { + byte pMemory[1024]; + int i; + for ( i = 0; i < 1024; i++ ) + { + pMemory[i] = rand(); + } + for ( i = 0; i < 50; i++ ) + { + sqrt( (float)HashBlock( pMemory, 1024 ) + HashBlock( pMemory, 1024 ) + 10.0 ); + } + bDoWork = false; + } + if ( m_nCount == g_nTotalToComplete ) + g_done.Set(); + return 0; + } + + static CInterlockedInt m_nCount; + bool bDoWork; +}; +CInterlockedInt CCountJob::m_nCount; +int g_nTotalAtFinish; + +void Test( bool bDistribute, bool bSleep = true, bool bFinishExecute = false, bool bDoWork = false ) +{ + for ( int bInterleavePushPop = 0; bInterleavePushPop < 2; bInterleavePushPop++ ) + { + for ( g_iSleep = -10; g_iSleep <= 10; g_iSleep += 10 ) + { + Msg( "ThreadPoolTest: Testing! Sleep %d, interleave %d \n", g_iSleep, bInterleavePushPop ); + int nMaxThreads = ( IsX360() ) ? 6 : 8; + int nIncrement = ( IsX360() ) ? 1 : 2; + for ( int i = 1; i <= nMaxThreads; i += nIncrement ) + { + CCountJob::m_nCount = 0; + g_nTotalAtFinish = 0; + ThreadPoolStartParams_t params; + params.nThreads = i; + params.fDistribute = ( bDistribute) ? TRS_TRUE : TRS_FALSE; + g_pTestThreadPool->Start( params, "Tst" ); + if ( !bInterleavePushPop ) + { + g_pTestThreadPool->SuspendExecution(); + } + + CCountJob jobs[4000]; + g_nTotalToComplete = ARRAYSIZE(jobs); + + CFastTimer timer, suspendTimer; + + suspendTimer.Start(); + timer.Start(); + for ( int j = 0; j < ARRAYSIZE(jobs); j++ ) + { + jobs[j].SetFlags( JF_QUEUE ); + jobs[j].bDoWork = bDoWork; + g_pTestThreadPool->AddJob( &jobs[j] ); + if ( bSleep && j % 16 == 0 ) + { + ThreadSleep( 0 ); + } + } + if ( !bInterleavePushPop ) + { + g_pTestThreadPool->ResumeExecution(); + } + if ( bFinishExecute && g_iSleep <= 1 ) + { + g_done.Wait(); + } + g_nTotalAtFinish = CCountJob::m_nCount; + timer.End(); + g_pTestThreadPool->SuspendExecution(); + suspendTimer.End(); + g_pTestThreadPool->ResumeExecution(); + g_pTestThreadPool->Stop(); + g_done.Reset(); + + int counts[8] = { 0 }; + for ( int j = 0; j < ARRAYSIZE(jobs); j++ ) + { + if ( jobs[j].GetServiceThread() != -1 ) + { + counts[jobs[j].GetServiceThread()]++; + jobs[j].ClearServiceThread(); + } + } + + Msg( "ThreadPoolTest: %d threads -- %d (%d) jobs processed in %fms, %fms to suspend (%f/%f) [%d, %d, %d, %d, %d, %d, %d, %d]\n", + i, g_nTotalAtFinish, (int)CCountJob::m_nCount, timer.GetDuration().GetMillisecondsF(), suspendTimer.GetDuration().GetMillisecondsF() - timer.GetDuration().GetMillisecondsF(), + timer.GetDuration().GetMillisecondsF() / (float)CCountJob::m_nCount, (suspendTimer.GetDuration().GetMillisecondsF())/(float)g_nTotalAtFinish, + counts[0], counts[1], counts[2], counts[3], counts[4], counts[5], counts[6], counts[7] ); + } + } + } +} + + +bool g_bOutputError; +volatile int g_ReadyToExecute; +CInterlockedInt g_nReady; + +class CExecuteTestJob : public CJob +{ +public: + virtual JobStatus_t DoExecute() + { + byte pMemory[1024]; + int i; + for ( i = 0; i < 1024; i++ ) + { + pMemory[i] = rand(); + } + for ( i = 0; i < 50; i++ ) + { + sqrt( (float)HashBlock( pMemory, 1024 ) + HashBlock( pMemory, 1024 ) + 10.0 ); + } + if ( AccessEvent()->Check() || IsFinished() ) + { + if ( !g_bOutputError ) + { + Msg( "Forced execute test failed!\n" ); + DebuggerBreakIfDebugging(); + } + } + return 0; + } +}; + +class CExecuteTestExecuteJob : public CJob +{ +public: + virtual JobStatus_t DoExecute() + { + bool bAbort = ( RandomInt( 1, 10 ) == 1 ); + g_nReady++; + while ( !g_ReadyToExecute ) + { + ThreadPause(); + } + + if ( !bAbort ) + m_pTestJob->Execute(); + else + m_pTestJob->Abort(); + g_nReady--; + return 0; + } + + CExecuteTestJob *m_pTestJob; +}; + + +void TestForcedExecute() +{ + Msg( "TestForcedExecute\n" ); + for ( int tests = 0; tests < 30; tests++ ) + { + for ( int i = 1; i <= 5; i += 2 ) + { + g_nReady = 0; + ThreadPoolStartParams_t params; + params.nThreads = i; + params.fDistribute = TRS_TRUE; + g_pTestThreadPool->Start( params, "Tst" ); + + static CExecuteTestJob jobs[4000]; + for ( int j = 0; j < ARRAYSIZE(jobs); j++ ) + { + g_ReadyToExecute = false; + for ( int k = 0; k < i; k++ ) + { + CExecuteTestExecuteJob *pJob = new CExecuteTestExecuteJob; + pJob->SetFlags( JF_QUEUE ); + pJob->m_pTestJob = &jobs[j]; + g_pTestThreadPool->AddJob( pJob ); + pJob->Release(); + } + while ( g_nReady < i ) + { + ThreadPause(); + } + g_ReadyToExecute = true; + ThreadSleep(); + jobs[j].Execute(); + while ( g_nReady > 0 ) + { + ThreadPause(); + } + } + g_pTestThreadPool->Stop(); + } + } + Msg( "TestForcedExecute DONE\n" ); +} + +} // namespace ThreadPoolTest + +void RunThreadPoolTests() +{ + CThreadPool pool; + ThreadPoolTest::g_pTestThreadPool = &pool; + RunTSQueueTests(10000); + RunTSListTests(10000); + +#ifdef _WIN32 + DWORD_PTR mask1 = 0; + --mask1; + DWORD_PTR mask2 = 0; + --mask2; + GetProcessAffinityMask( GetCurrentProcess(), &mask1, &mask2 ); +#else + int32 mask1=-1; +#endif + Msg( "ThreadPoolTest: Job distribution speed\n" ); + for ( int i = 0; i < 2; i++ ) + { + bool bToCompletion = ( i % 2 != 0 ); + if ( !IsX360() ) + { + Msg( "ThreadPoolTest: Non-distribute\n" ); + ThreadPoolTest::Test( false, true, bToCompletion ); + } + + Msg( "ThreadPoolTest: Distribute\n" ); + ThreadPoolTest::Test( true, true, bToCompletion ); + + Msg( "ThreadPoolTest: One core\n" ); + ThreadSetAffinity( 0, 1 ); + ThreadPoolTest::Test( false, true, bToCompletion ); + ThreadSetAffinity( 0, mask1 ); + + Msg( "ThreadPoolTest: NO Sleep\n" ); + ThreadPoolTest::Test( false, false, bToCompletion ); + + Msg( "ThreadPoolTest: Distribute\n" ); + ThreadPoolTest::Test( true, false, bToCompletion ); + + Msg( "ThreadPoolTest: One core\n" ); + ThreadSetAffinity( 0, 1 ); + ThreadPoolTest::Test( false, false, bToCompletion ); + ThreadSetAffinity( 0, mask1 ); + } + + Msg( "ThreadPoolTest: Jobs doing work\n" ); + for ( int i = 0; i < 2; i++ ) + { + bool bToCompletion = true;// = ( i % 2 != 0 ); + if ( !IsX360() ) + { + Msg( "ThreadPoolTest: Non-distribute\n" ); + ThreadPoolTest::Test( false, true, bToCompletion, true ); + } + + Msg( "ThreadPoolTest: Distribute\n" ); + ThreadPoolTest::Test( true, true, bToCompletion, true ); + + Msg( "ThreadPoolTest: One core\n" ); + ThreadSetAffinity( 0, 1 ); + ThreadPoolTest::Test( false, true, bToCompletion, true ); + ThreadSetAffinity( 0, mask1 ); + + Msg( "ThreadPoolTest: NO Sleep\n" ); + ThreadPoolTest::Test( false, false, bToCompletion, true ); + + Msg( "ThreadPoolTest: Distribute\n" ); + ThreadPoolTest::Test( true, false, bToCompletion, true ); + + Msg( "ThreadPoolTest: One core\n" ); + ThreadSetAffinity( 0, 1 ); + ThreadPoolTest::Test( false, false, bToCompletion, true ); + ThreadSetAffinity( 0, mask1 ); + } +#ifdef _WIN32 + GetProcessAffinityMask( GetCurrentProcess(), &mask1, &mask2 ); +#endif + + ThreadPoolTest::TestForcedExecute(); +} diff --git a/vstdlib/osversion.cpp b/vstdlib/osversion.cpp new file mode 100644 index 0000000..580b02f --- /dev/null +++ b/vstdlib/osversion.cpp @@ -0,0 +1,447 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vstdlib/osversion.h" +#include "winlite.h" +#include "strtools.h" +#include "tier0/dbg.h" + +#ifdef OSX +#include +#endif + +//----------------------------------------------------------------------------- +// Purpose: return the OS type for this machine +//----------------------------------------------------------------------------- +EOSType GetOSType() +{ + static EOSType eOSVersion = k_eOSUnknown; + +#if defined( _WIN32 ) && !defined( _X360 ) + if ( eOSVersion == k_eOSUnknown || eOSVersion == k_eWinUnknown ) + { + eOSVersion = k_eWinUnknown; + OSVERSIONINFOEX osvi; + Q_memset( &osvi, 0x00, sizeof(osvi) ); + osvi.dwOSVersionInfoSize = sizeof(osvi); + + if ( GetVersionEx( (OSVERSIONINFO *) &osvi ) ) + { + switch ( osvi.dwPlatformId ) + { + case VER_PLATFORM_WIN32_NT: + if ( osvi.dwMajorVersion <= 4 ) + { + eOSVersion = k_eWinNT; + } + else if ( osvi.dwMajorVersion == 5 ) + { + switch( osvi.dwMinorVersion ) + { + case 0: + eOSVersion = k_eWin2000; + break; + case 1: + eOSVersion = k_eWinXP; + break; + case 2: + eOSVersion = k_eWin2003; + break; + } + } + else if ( osvi.dwMajorVersion >= 6 ) + { + if ( osvi.wProductType == VER_NT_WORKSTATION ) + { + switch ( osvi.dwMinorVersion ) + { + case 0: + eOSVersion = k_eWinVista; + break; + case 1: + eOSVersion = k_eWindows7; + break; + } + } + else /* ( osvi.wProductType != VER_NT_WORKSTATION ) */ + { + switch ( osvi.dwMinorVersion ) + { + case 0: + eOSVersion = k_eWin2008; // Windows 2008, not R2 + break; + case 1: + eOSVersion = k_eWin2008; // Windows 2008 R2 + break; + } + } + } + break; + case VER_PLATFORM_WIN32_WINDOWS: + switch ( osvi.dwMinorVersion ) + { + case 0: + eOSVersion = k_eWin95; + break; + case 10: + eOSVersion = k_eWin98; + break; + case 90: + eOSVersion = k_eWinME; + break; + } + break; + case VER_PLATFORM_WIN32s: + eOSVersion = k_eWin311; + break; + } + } + } +#elif defined(OSX) + if ( eOSVersion == k_eOSUnknown ) + { + SInt32 MajorVer = 0; + SInt32 MinorVer = 0; + SInt32 PatchVer = 0; + OSErr err = noErr; + err = Gestalt( gestaltSystemVersionMajor, &MajorVer ); + if ( err != noErr ) + return k_eOSUnknown; + err = Gestalt( gestaltSystemVersionMinor, &MinorVer ); + if ( err != noErr ) + return k_eOSUnknown; + err = Gestalt( gestaltSystemVersionBugFix, &PatchVer ); + if ( err != noErr ) + return k_eOSUnknown; + + switch ( MajorVer ) + { + case 10: + { + switch( MinorVer ) + { + case 4: + eOSVersion = k_eMacOS104; + break; + case 5: + eOSVersion = k_eMacOS105; + switch ( PatchVer ) + { + case 8: + eOSVersion = k_eMacOS1058; + default: + break; + } + break; + case 6: + eOSVersion = k_eMacOS106; + switch ( PatchVer ) + { + case 1: + case 2: + break; + case 3: + default: + // note the default here - 10.6.4 (5,6...) >= 10.6.3, so we want to + // identify as 10.6.3 for sysreqs purposes + eOSVersion = k_eMacOS1063; + break; + } + break; + case 7: + eOSVersion = k_eMacOS107; + break; + default: + break; + } + } + default: + break; + } + } +#elif defined(LINUX) + if ( eOSVersion == k_eOSUnknown ) + { + + FILE *fpKernelVer = fopen( "/proc/version", "r" ); + + if ( !fpKernelVer ) + return k_eLinuxUnknown; + + char rgchVersionLine[1024]; + char *pchRet = fgets( rgchVersionLine, sizeof(rgchVersionLine), fpKernelVer ); + fclose( fpKernelVer ); + + eOSVersion = k_eLinuxUnknown; + + // move past "Linux version " + const char *pchVersion = rgchVersionLine + Q_strlen( "Linux version " ); + if ( pchRet && *pchVersion == '2' && *(pchVersion+1) == '.' ) + { + pchVersion += 2; // move past "2." + if ( *pchVersion == '2' && *(pchVersion+1) == '.' ) + eOSVersion = k_eLinux22; + else if ( *pchVersion == '4' && *(pchVersion+1) == '.' ) + eOSVersion = k_eLinux24; + else if ( *pchVersion == '6' && *(pchVersion+1) == '.' ) + eOSVersion = k_eLinux26; + } + } +#endif + return eOSVersion; +} + +//----------------------------------------------------------------------------- +// Purpose: get platform-specific OS details (distro, on linux) +// returns a pointer to the input buffer on success (for convenience), +// NULL on failure. +//----------------------------------------------------------------------------- +const char *GetOSDetailString( char *pchOutBuf, int cchOutBuf ) +{ +#if defined WIN32 + (void)( pchOutBuf ); + (void)( cchOutBuf ); + // no interesting details + return NULL; +#else +#if defined LINUX + // we're about to go poking around to see if we can figure out distribution + // looking @ any /etc file is fragile (people can change 'em), + // but since this is just hardware survey data, we're not super concerned. + // a bunch of OS-specific issue files + const char *pszIssueFile[] = + { + "/etc/redhat-release", + "/etc/fedora-release", + "/etc/slackware-release", + "/etc/debian_release", + "/etc/mandrake-release", + "/etc/yellowdog-release", + "/etc/gentoo-release", + "/etc/lsb-release", + "/etc/SUSE-release", + }; + if ( !pchOutBuf ) + return NULL; + + for (int i = 0; i < Q_ARRAYSIZE( pszIssueFile ); i++ ) + { + FILE *fdInfo = fopen( pszIssueFile[i], "r" ); + if ( !fdInfo ) + continue; + + // prepend the buffer with the name of the file we found for easier grouping + snprintf( pchOutBuf, cchOutBuf, "%s\n", pszIssueFile[i] ); + int cchIssueFile = strlen( pszIssueFile[i] ) + 1; + ssize_t cubRead = fread( (void*) (pchOutBuf + cchIssueFile) , sizeof(char), cchOutBuf - cchIssueFile, fdInfo ); + fclose( fdInfo ); + + if ( cubRead < 0 ) + return NULL; + + // null terminate + pchOutBuf[ MIN( cubRead, cchOutBuf-1 ) ] = '\0'; + return pchOutBuf; + } +#endif + // if all else fails, just send back uname -a + if ( !pchOutBuf ) + return NULL; + FILE *fpUname = popen( "uname -mrsv", "r" ); + if ( !fpUname ) + return NULL; + size_t cchRead = fread( pchOutBuf, sizeof(char), cchOutBuf, fpUname ); + pclose( fpUname ); + + pchOutBuf[ MIN( cchRead, (size_t)cchOutBuf-1 ) ] = '\0'; + return pchOutBuf; +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: get a friendly name for an OS type +//----------------------------------------------------------------------------- +const char *GetNameFromOSType( EOSType eOSType ) +{ + switch ( eOSType ) + { + case k_eWinUnknown: + return "Windows"; + case k_eWin311: + return "Windows 3.11"; + case k_eWin95: + return "Windows 95"; + case k_eWin98: + return "Windows 98"; + case k_eWinME: + return "Windows ME"; + case k_eWinNT: + return "Windows NT"; + case k_eWin2000: + return "Windows 2000"; + case k_eWinXP: + return "Windows XP"; + case k_eWin2003: + return "Windows 2003"; + case k_eWinVista: + return "Windows Vista"; + case k_eWindows7: + return "Windows 7"; + case k_eWin2008: + return "Windows 2008"; +#ifdef POSIX + case k_eMacOSUnknown: + return "Mac OS"; + case k_eMacOS104: + return "MacOS 10.4"; + case k_eMacOS105: + return "MacOS 10.5"; + case k_eMacOS1058: + return "MacOS 10.5.8"; + case k_eMacOS106: + return "MacOS 10.6"; + case k_eMacOS1063: + return "MacOS 10.6.3"; + case k_eMacOS107: + return "MacOS 10.7"; + case k_eLinuxUnknown: + return "Linux"; + case k_eLinux22: + return "Linux 2.2"; + case k_eLinux24: + return "Linux 2.4"; + case k_eLinux26: + return "Linux 2.6"; +#endif + default: + case k_eOSUnknown: + return "Unknown"; + } +} + + +// friendly name to OS type, MUST be same size as EOSType enum +struct OSTypeNameTuple +{ + EOSType m_OSType; + const char *m_pchOSName; + +}; + +const OSTypeNameTuple k_rgOSTypeToName[] = +{ + { k_eOSUnknown, "unknown" }, + { k_eMacOSUnknown, "macos" }, + { k_eMacOS104, "macos104" }, + { k_eMacOS105, "macos105" }, + { k_eMacOS1058, "macos1058" }, + { k_eMacOS106, "macos106" }, + { k_eMacOS1063, "macos1063" }, + { k_eMacOS107, "macos107" }, + { k_eLinuxUnknown, "linux" }, + { k_eLinux22, "linux22" }, + { k_eLinux24, "linux24" }, + { k_eLinux26, "linux26" }, + { k_eWinUnknown, "windows" }, + { k_eWin311, "win311" }, + { k_eWin95, "win95" }, + { k_eWin98, "win98" }, + { k_eWinME, "winME" }, + { k_eWinNT, "winNT" }, + { k_eWin2000, "win200" }, + { k_eWinXP, "winXP" }, + { k_eWin2003, "win2003" }, + { k_eWinVista, "winVista" }, + { k_eWindows7, "win7" }, + { k_eWin2008, "win2008" }, +}; + +//----------------------------------------------------------------------------- +// Purpose: convert a friendly OS name to a eostype +//----------------------------------------------------------------------------- +EOSType GetOSTypeFromString_Deprecated( const char *pchName ) +{ + EOSType eOSType; +#ifdef WIN32 + eOSType = k_eWinUnknown; +#else + eOSType = k_eOSUnknown; +#endif + + // if this fires, make sure all OS types are in the map + Assert( Q_ARRAYSIZE( k_rgOSTypeToName ) == k_eOSTypeMax ); + if ( !pchName || Q_strlen( pchName ) == 0 ) + return eOSType; + + for ( int iOS = 0; iOS < Q_ARRAYSIZE( k_rgOSTypeToName ) ; iOS++ ) + { + if ( !Q_stricmp( k_rgOSTypeToName[iOS].m_pchOSName, pchName ) ) + return k_rgOSTypeToName[iOS].m_OSType; + } + return eOSType; +} + +bool OSTypesAreCompatible( EOSType eOSTypeDetected, EOSType eOSTypeRequired ) +{ + // check windows (on the positive side of the number line) + if ( eOSTypeRequired >= k_eWinUnknown ) + return ( eOSTypeDetected >= eOSTypeRequired ); + + if ( eOSTypeRequired == k_eOSUnknown ) + return true; + + // osx + if ( eOSTypeRequired >= k_eMacOSUnknown && eOSTypeRequired < k_eOSUnknown ) + return ( eOSTypeDetected >= eOSTypeRequired && eOSTypeDetected < k_eOSUnknown ); + + // and linux + if ( eOSTypeRequired >= k_eLinuxUnknown && eOSTypeRequired < k_eMacOSUnknown ) + return ( eOSTypeDetected >= eOSTypeRequired && eOSTypeDetected < k_eMacOSUnknown ); + + return false; +} + +bool Is64BitOS() +{ +#if defined OSX + return true; +#elif defined LINUX + FILE* pp = popen("uname -m", "r"); + if (pp != NULL) + { + char rgchArchString[256]; + fgets(rgchArchString, sizeof(rgchArchString), pp); + pclose(pp); + if (!strncasecmp(rgchArchString, "x86_64", strlen("x86_64"))) + return true; + } +#else + Assert(!"implement Is64BitOS"); +#endif + return false; +} + +// these strings "windows", "macos", "linux" are part of the +// interface, which is why they're hard-coded here, rather than using +// the strings in k_rgOSTypeToName +const char *GetPlatformName( bool *pbIs64Bit ) +{ + if ( pbIs64Bit ) + *pbIs64Bit = Is64BitOS(); + + EOSType eType = GetOSType(); + if ( OSTypesAreCompatible( eType, k_eWinUnknown ) ) + return "windows"; + if ( OSTypesAreCompatible( eType, k_eMacOSUnknown ) ) + return "macos"; + if ( OSTypesAreCompatible( eType, k_eLinuxUnknown ) ) + return "linux"; + return "unknown"; +} + diff --git a/vstdlib/processutils.cpp b/vstdlib/processutils.cpp new file mode 100644 index 0000000..dc4ee59 --- /dev/null +++ b/vstdlib/processutils.cpp @@ -0,0 +1,473 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#if !defined( _X360 ) +#include +#endif +#include "vstdlib/iprocessutils.h" +#include "tier1/utllinkedlist.h" +#include "tier1/utlstring.h" +#include "tier1/utlbuffer.h" +#include "tier1/tier1.h" + +//----------------------------------------------------------------------------- +// At the moment, we can only run one process at a time +//----------------------------------------------------------------------------- +class CProcessUtils : public CTier1AppSystem< IProcessUtils > +{ + typedef CTier1AppSystem< IProcessUtils > BaseClass; + +public: + CProcessUtils() : BaseClass( false ) {} + + // Inherited from IAppSystem + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // Inherited from IProcessUtils + virtual ProcessHandle_t StartProcess( const char *pCommandLine, bool bConnectStdPipes ); + virtual ProcessHandle_t StartProcess( int argc, const char **argv, bool bConnectStdPipes ); + virtual void CloseProcess( ProcessHandle_t hProcess ); + virtual void AbortProcess( ProcessHandle_t hProcess ); + virtual bool IsProcessComplete( ProcessHandle_t hProcess ); + virtual void WaitUntilProcessCompletes( ProcessHandle_t hProcess ); + virtual int SendProcessInput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ); + virtual int GetProcessOutputSize( ProcessHandle_t hProcess ); + virtual int GetProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ); + virtual int GetProcessExitCode( ProcessHandle_t hProcess ); + +private: + struct ProcessInfo_t + { + HANDLE m_hChildStdinRd; + HANDLE m_hChildStdinWr; + HANDLE m_hChildStdoutRd; + HANDLE m_hChildStdoutWr; + HANDLE m_hChildStderrWr; + HANDLE m_hProcess; + CUtlString m_CommandLine; + CUtlBuffer m_ProcessOutput; + }; + + // Returns the last error that occurred + char *GetErrorString( char *pBuf, int nBufLen ); + + // creates the process, adds it to the list and writes the windows HANDLE into info.m_hProcess + ProcessHandle_t CreateProcess( ProcessInfo_t &info, bool bConnectStdPipes ); + + // Shuts down the process handle + void ShutdownProcess( ProcessHandle_t hProcess ); + + // Methods used to read output back from a process + int GetActualProcessOutputSize( ProcessHandle_t hProcess ); + int GetActualProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ); + + CUtlFixedLinkedList< ProcessInfo_t > m_Processes; + ProcessHandle_t m_hCurrentProcess; + bool m_bInitialized; +}; + + +//----------------------------------------------------------------------------- +// Purpose: singleton accessor +//----------------------------------------------------------------------------- +static CProcessUtils s_ProcessUtils; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CProcessUtils, IProcessUtils, PROCESS_UTILS_INTERFACE_VERSION, s_ProcessUtils ); + + +//----------------------------------------------------------------------------- +// Initialize, shutdown process system +//----------------------------------------------------------------------------- +InitReturnVal_t CProcessUtils::Init() +{ + InitReturnVal_t nRetVal = BaseClass::Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + m_bInitialized = true; + m_hCurrentProcess = PROCESS_HANDLE_INVALID; + return INIT_OK; +} + +void CProcessUtils::Shutdown() +{ + Assert( m_bInitialized ); + Assert( m_Processes.Count() == 0 ); + if ( m_Processes.Count() != 0 ) + { + AbortProcess( m_hCurrentProcess ); + } + m_bInitialized = false; + return BaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Returns the last error that occurred +//----------------------------------------------------------------------------- +char *CProcessUtils::GetErrorString( char *pBuf, int nBufLen ) +{ + FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, pBuf, nBufLen, NULL ); + char *p = strchr(pBuf, '\r'); // get rid of \r\n + if(p) + { + p[0] = 0; + } + return pBuf; +} + + +ProcessHandle_t CProcessUtils::CreateProcess( ProcessInfo_t &info, bool bConnectStdPipes ) +{ + STARTUPINFO si; + memset(&si, 0, sizeof si); + si.cb = sizeof(si); + if ( bConnectStdPipes ) + { + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = info.m_hChildStdinRd; + si.hStdError = info.m_hChildStderrWr; + si.hStdOutput = info.m_hChildStdoutWr; + } + + PROCESS_INFORMATION pi; + if ( ::CreateProcess( NULL, info.m_CommandLine.GetForModify(), NULL, NULL, TRUE, DETACHED_PROCESS, NULL, NULL, &si, &pi ) ) + { + info.m_hProcess = pi.hProcess; + m_hCurrentProcess = m_Processes.AddToTail( info ); + return m_hCurrentProcess; + } + + char buf[ 512 ]; + Warning( "Could not execute the command:\n %s\n" + "Windows gave the error message:\n \"%s\"\n", + info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) ); + + return PROCESS_HANDLE_INVALID; +} + +//----------------------------------------------------------------------------- +// Options for compilation +//----------------------------------------------------------------------------- +ProcessHandle_t CProcessUtils::StartProcess( const char *pCommandLine, bool bConnectStdPipes ) +{ + Assert( m_bInitialized ); + + // NOTE: For the moment, we can only run one process at a time + // although in the future, I expect to have a process queue. + if ( m_hCurrentProcess != PROCESS_HANDLE_INVALID ) + { + WaitUntilProcessCompletes( m_hCurrentProcess ); + } + + ProcessInfo_t info; + info.m_CommandLine = pCommandLine; + + if ( !bConnectStdPipes ) + { + info.m_hChildStderrWr = INVALID_HANDLE_VALUE; + info.m_hChildStdinRd = info.m_hChildStdinWr = INVALID_HANDLE_VALUE; + info.m_hChildStdoutRd = info.m_hChildStdoutWr = INVALID_HANDLE_VALUE; + + return CreateProcess( info, false ); + } + + SECURITY_ATTRIBUTES saAttr; + + // Set the bInheritHandle flag so pipe handles are inherited. + saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + saAttr.bInheritHandle = TRUE; + saAttr.lpSecurityDescriptor = NULL; + + // Create a pipe for the child's STDOUT. + if ( CreatePipe( &info.m_hChildStdoutRd, &info.m_hChildStdoutWr, &saAttr, 0 ) ) + { + if ( CreatePipe( &info.m_hChildStdinRd, &info.m_hChildStdinWr, &saAttr, 0 ) ) + { + if ( DuplicateHandle( GetCurrentProcess(), info.m_hChildStdoutWr, GetCurrentProcess(), + &info.m_hChildStderrWr, 0, TRUE, DUPLICATE_SAME_ACCESS ) ) + { +// _setmode( info.m_hChildStdoutRd, _O_TEXT ); +// _setmode( info.m_hChildStdoutWr, _O_TEXT ); +// _setmode( info.m_hChildStderrWr, _O_TEXT ); + + ProcessHandle_t hProcess = CreateProcess( info, true ); + if ( hProcess != PROCESS_HANDLE_INVALID ) + return hProcess; + + CloseHandle( info.m_hChildStderrWr ); + } + CloseHandle( info.m_hChildStdinRd ); + CloseHandle( info.m_hChildStdinWr ); + } + CloseHandle( info.m_hChildStdoutRd ); + CloseHandle( info.m_hChildStdoutWr ); + } + return PROCESS_HANDLE_INVALID; +} + + +//----------------------------------------------------------------------------- +// Start up a process +//----------------------------------------------------------------------------- +ProcessHandle_t CProcessUtils::StartProcess( int argc, const char **argv, bool bConnectStdPipes ) +{ + CUtlString commandLine; + for ( int i = 0; i < argc; ++i ) + { + commandLine += argv[i]; + if ( i != argc-1 ) + { + commandLine += " "; + } + } + return StartProcess( commandLine.Get(), bConnectStdPipes ); +} + + +//----------------------------------------------------------------------------- +// Shuts down the process handle +//----------------------------------------------------------------------------- +void CProcessUtils::ShutdownProcess( ProcessHandle_t hProcess ) +{ + ProcessInfo_t& info = m_Processes[hProcess]; + CloseHandle( info.m_hChildStderrWr ); + CloseHandle( info.m_hChildStdinRd ); + CloseHandle( info.m_hChildStdinWr ); + CloseHandle( info.m_hChildStdoutRd ); + CloseHandle( info.m_hChildStdoutWr ); + + m_Processes.Remove( hProcess ); +} + + +//----------------------------------------------------------------------------- +// Closes the process +//----------------------------------------------------------------------------- +void CProcessUtils::CloseProcess( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + if ( hProcess != PROCESS_HANDLE_INVALID ) + { + WaitUntilProcessCompletes( hProcess ); + ShutdownProcess( hProcess ); + } +} + + +//----------------------------------------------------------------------------- +// Aborts the process +//----------------------------------------------------------------------------- +void CProcessUtils::AbortProcess( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + if ( hProcess != PROCESS_HANDLE_INVALID ) + { + if ( !IsProcessComplete( hProcess ) ) + { + ProcessInfo_t& info = m_Processes[hProcess]; + TerminateProcess( info.m_hProcess, 1 ); + } + ShutdownProcess( hProcess ); + } +} + + +//----------------------------------------------------------------------------- +// Returns true if the process is complete +//----------------------------------------------------------------------------- +bool CProcessUtils::IsProcessComplete( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + Assert( hProcess != PROCESS_HANDLE_INVALID ); + if ( m_hCurrentProcess != hProcess ) + return true; + + HANDLE h = m_Processes[hProcess].m_hProcess; + return ( WaitForSingleObject( h, 0 ) != WAIT_TIMEOUT ); +} + + +//----------------------------------------------------------------------------- +// Methods used to write input into a process +//----------------------------------------------------------------------------- +int CProcessUtils::SendProcessInput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ) +{ + // Unimplemented yet + Assert( 0 ); + return 0; +} + + +//----------------------------------------------------------------------------- +// Methods used to read output back from a process +//----------------------------------------------------------------------------- +int CProcessUtils::GetActualProcessOutputSize( ProcessHandle_t hProcess ) +{ + Assert( hProcess != PROCESS_HANDLE_INVALID ); + + ProcessInfo_t& info = m_Processes[ hProcess ]; + if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE ) + return 0; + + DWORD dwCount = 0; + if ( !PeekNamedPipe( info.m_hChildStdoutRd, NULL, NULL, NULL, &dwCount, NULL ) ) + { + char buf[ 512 ]; + Warning( "Could not read from pipe associated with command %s\n" + "Windows gave the error message:\n \"%s\"\n", + info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) ); + return 0; + } + + // Add 1 for auto-NULL termination + return ( dwCount > 0 ) ? (int)dwCount + 1 : 0; +} + +int CProcessUtils::GetActualProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ) +{ + ProcessInfo_t& info = m_Processes[ hProcess ]; + if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE ) + return 0; + + DWORD dwCount = 0; + DWORD dwRead = 0; + + // FIXME: Is there a way of making pipes be text mode so we don't get /n/rs back? + char *pTempBuf = (char*)_alloca( nBufLen ); + if ( !PeekNamedPipe( info.m_hChildStdoutRd, NULL, NULL, NULL, &dwCount, NULL ) ) + { + char buf[ 512 ]; + Warning( "Could not read from pipe associated with command %s\n" + "Windows gave the error message:\n \"%s\"\n", + info.m_CommandLine.Get(), GetErrorString( buf, sizeof(buf) ) ); + return 0; + } + + dwCount = min( dwCount, (DWORD)nBufLen - 1 ); + ReadFile( info.m_hChildStdoutRd, pTempBuf, dwCount, &dwRead, NULL); + + // Convert /n/r -> /n + int nActualCountRead = 0; + for ( unsigned int i = 0; i < dwRead; ++i ) + { + char c = pTempBuf[i]; + if ( c == '\r' ) + { + if ( ( i+1 < dwRead ) && ( pTempBuf[i+1] == '\n' ) ) + { + pBuf[nActualCountRead++] = '\n'; + ++i; + continue; + } + } + + pBuf[nActualCountRead++] = c; + } + + return nActualCountRead; +} + + +int CProcessUtils::GetProcessOutputSize( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + if ( hProcess == PROCESS_HANDLE_INVALID ) + return 0; + + return GetActualProcessOutputSize( hProcess ) + m_Processes[hProcess].m_ProcessOutput.TellPut(); +} + + +int CProcessUtils::GetProcessOutput( ProcessHandle_t hProcess, char *pBuf, int nBufLen ) +{ + Assert( m_bInitialized ); + + if ( hProcess == PROCESS_HANDLE_INVALID ) + return 0; + + ProcessInfo_t &info = m_Processes[hProcess]; + int nCachedBytes = info.m_ProcessOutput.TellPut(); + int nBytesRead = 0; + if ( nCachedBytes ) + { + nBytesRead = min( nBufLen-1, nCachedBytes ); + info.m_ProcessOutput.Get( pBuf, nBytesRead ); + pBuf[ nBytesRead ] = 0; + nBufLen -= nBytesRead; + pBuf += nBytesRead; + if ( info.m_ProcessOutput.GetBytesRemaining() == 0 ) + { + info.m_ProcessOutput.Purge(); + } + + if ( nBufLen <= 1 ) + return nBytesRead; + } + + // Auto-NULL terminate + int nActualCountRead = GetActualProcessOutput( hProcess, pBuf, nBufLen ); + pBuf[nActualCountRead] = 0; + return nActualCountRead + nBytesRead + 1; +} + + +//----------------------------------------------------------------------------- +// Returns the exit code for the process. Doesn't work unless the process is complete +//----------------------------------------------------------------------------- +int CProcessUtils::GetProcessExitCode( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + ProcessInfo_t &info = m_Processes[hProcess]; + DWORD nExitCode; + BOOL bOk = GetExitCodeProcess( info.m_hProcess, &nExitCode ); + if ( !bOk || nExitCode == STILL_ACTIVE ) + return -1; + return nExitCode; +} + + +//----------------------------------------------------------------------------- +// Waits until a process is complete +//----------------------------------------------------------------------------- +void CProcessUtils::WaitUntilProcessCompletes( ProcessHandle_t hProcess ) +{ + Assert( m_bInitialized ); + + // For the moment, we can only run one process at a time + if ( ( hProcess == PROCESS_HANDLE_INVALID ) || ( m_hCurrentProcess != hProcess ) ) + return; + + ProcessInfo_t &info = m_Processes[ hProcess ]; + + if ( info.m_hChildStdoutRd == INVALID_HANDLE_VALUE ) + { + WaitForSingleObject( info.m_hProcess, INFINITE ); + } + else + { + // NOTE: The called process can block during writes to stderr + stdout + // if the pipe buffer is empty. Therefore, waiting INFINITE is not + // possible here. We must queue up messages received to allow the + // process to continue + while ( WaitForSingleObject( info.m_hProcess, 100 ) == WAIT_TIMEOUT ) + { + int nLen = GetActualProcessOutputSize( hProcess ); + if ( nLen > 0 ) + { + int nPut = info.m_ProcessOutput.TellPut(); + info.m_ProcessOutput.EnsureCapacity( nPut + nLen ); + int nBytesRead = GetActualProcessOutput( hProcess, (char*)info.m_ProcessOutput.PeekPut(), nLen ); + info.m_ProcessOutput.SeekPut( CUtlBuffer::SEEK_HEAD, nPut + nBytesRead ); + } + } + } + + m_hCurrentProcess = PROCESS_HANDLE_INVALID; +} + + + diff --git a/vstdlib/random.cpp b/vstdlib/random.cpp new file mode 100644 index 0000000..e8a757a --- /dev/null +++ b/vstdlib/random.cpp @@ -0,0 +1,265 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Random number generator +// +// $Workfile: $ +// $NoKeywords: $ +//===========================================================================// + + +#include "vstdlib/random.h" +#include +#include "dbg.h" + +#include "tier0/memdbgon.h" + +#define IA 16807 +#define IM 2147483647 +#define IQ 127773 +#define IR 2836 +#define NDIV (1+(IM-1)/NTAB) +#define MAX_RANDOM_RANGE 0x7FFFFFFFUL + +// fran1 -- return a random floating-point number on the interval [0,1) +// +#define AM (1.0/IM) +#define EPS 1.2e-7 +#define RNMX (1.0-EPS) + +//----------------------------------------------------------------------------- +// globals +//----------------------------------------------------------------------------- +static CUniformRandomStream s_UniformStream; +static CGaussianRandomStream s_GaussianStream; +static IUniformRandomStream *s_pUniformStream = &s_UniformStream; + + +//----------------------------------------------------------------------------- +// Installs a global random number generator, which will affect the Random functions above +//----------------------------------------------------------------------------- +void InstallUniformRandomStream( IUniformRandomStream *pStream ) +{ + s_pUniformStream = pStream ? pStream : &s_UniformStream; +} + + +//----------------------------------------------------------------------------- +// A couple of convenience functions to access the library's global uniform stream +//----------------------------------------------------------------------------- +void RandomSeed( int iSeed ) +{ + s_pUniformStream->SetSeed( iSeed ); +} + +float RandomFloat( float flMinVal, float flMaxVal ) +{ + return s_pUniformStream->RandomFloat( flMinVal, flMaxVal ); +} + +float RandomFloatExp( float flMinVal, float flMaxVal, float flExponent ) +{ + return s_pUniformStream->RandomFloatExp( flMinVal, flMaxVal, flExponent ); +} + +int RandomInt( int iMinVal, int iMaxVal ) +{ + return s_pUniformStream->RandomInt( iMinVal, iMaxVal ); +} + +float RandomGaussianFloat( float flMean, float flStdDev ) +{ + return s_GaussianStream.RandomFloat( flMean, flStdDev ); +} + + +//----------------------------------------------------------------------------- +// +// Implementation of the uniform random number stream +// +//----------------------------------------------------------------------------- +CUniformRandomStream::CUniformRandomStream() +{ + SetSeed(0); +} + +void CUniformRandomStream::SetSeed( int iSeed ) +{ + AUTO_LOCK( m_mutex ); + m_idum = ( ( iSeed < 0 ) ? iSeed : -iSeed ); + m_iy = 0; +} + +int CUniformRandomStream::GenerateRandomNumber() +{ + AUTO_LOCK( m_mutex ); + int j; + int k; + + if (m_idum <= 0 || !m_iy) + { + if (-(m_idum) < 1) + m_idum=1; + else + m_idum = -(m_idum); + + for ( j=NTAB+7; j>=0; j--) + { + k = (m_idum)/IQ; + m_idum = IA*(m_idum-k*IQ)-IR*k; + if (m_idum < 0) + m_idum += IM; + if (j < NTAB) + m_iv[j] = m_idum; + } + m_iy=m_iv[0]; + } + k=(m_idum)/IQ; + m_idum=IA*(m_idum-k*IQ)-IR*k; + if (m_idum < 0) + m_idum += IM; + j=m_iy/NDIV; + + // We're seeing some strange memory corruption in the contents of s_pUniformStream. + // Perhaps it's being caused by something writing past the end of this array? + // Bounds-check in release to see if that's the case. + if (j >= NTAB || j < 0) + { + DebuggerBreakIfDebugging(); + Warning("CUniformRandomStream had an array overrun: tried to write to element %d of 0..31. Contact Tom or Elan.\n", j); + // Ensure that NTAB is a power of two. + COMPILE_TIME_ASSERT( ( NTAB & ( NTAB - 1 ) ) == 0 ); + // Clamp j. + j &= NTAB - 1; + } + + m_iy=m_iv[j]; + m_iv[j] = m_idum; + + return m_iy; +} + +float CUniformRandomStream::RandomFloat( float flLow, float flHigh ) +{ + // float in [0,1) + float fl = AM * GenerateRandomNumber(); + if (fl > RNMX) + { + fl = RNMX; + } + return (fl * ( flHigh - flLow ) ) + flLow; // float in [low,high) +} + +float CUniformRandomStream::RandomFloatExp( float flMinVal, float flMaxVal, float flExponent ) +{ + // float in [0,1) + float fl = AM * GenerateRandomNumber(); + if (fl > RNMX) + { + fl = RNMX; + } + if ( flExponent != 1.0f ) + { + fl = powf( fl, flExponent ); + } + return (fl * ( flMaxVal - flMinVal ) ) + flMinVal; // float in [low,high) +} + +int CUniformRandomStream::RandomInt( int iLow, int iHigh ) +{ + //ASSERT(lLow <= lHigh); + unsigned int maxAcceptable; + unsigned int x = iHigh-iLow+1; + unsigned int n; + + // If you hit either of these assert, you're not getting back the random number that you thought you were. + Assert( x == iHigh-(int64)iLow+1 ); // Check that we didn't overflow int + Assert( x-1 <= MAX_RANDOM_RANGE ); // Check that the values provide an acceptable range + + if (x <= 1 || MAX_RANDOM_RANGE < x-1) + { + Assert( iLow == iHigh ); // This is the only time it is OK to have a range containing a single number + return iLow; + } + + // The following maps a uniform distribution on the interval [0,MAX_RANDOM_RANGE] + // to a smaller, client-specified range of [0,x-1] in a way that doesn't bias + // the uniform distribution unfavorably. Even for a worst case x, the loop is + // guaranteed to be taken no more than half the time, so for that worst case x, + // the average number of times through the loop is 2. For cases where x is + // much smaller than MAX_RANDOM_RANGE, the average number of times through the + // loop is very close to 1. + // + maxAcceptable = MAX_RANDOM_RANGE - ((MAX_RANDOM_RANGE+1) % x ); + do + { + n = GenerateRandomNumber(); + } while (n > maxAcceptable); + + return iLow + (n % x); +} + + +//----------------------------------------------------------------------------- +// +// Implementation of the gaussian random number stream +// We're gonna use the Box-Muller method (which actually generates 2 +// gaussian-distributed numbers at once) +// +//----------------------------------------------------------------------------- +CGaussianRandomStream::CGaussianRandomStream( IUniformRandomStream *pUniformStream ) +{ + AttachToStream( pUniformStream ); +} + + +//----------------------------------------------------------------------------- +// Attaches to a random uniform stream +//----------------------------------------------------------------------------- +void CGaussianRandomStream::AttachToStream( IUniformRandomStream *pUniformStream ) +{ + AUTO_LOCK( m_mutex ); + m_pUniformStream = pUniformStream; + m_bHaveValue = false; +} + + +//----------------------------------------------------------------------------- +// Generates random numbers +//----------------------------------------------------------------------------- +float CGaussianRandomStream::RandomFloat( float flMean, float flStdDev ) +{ + AUTO_LOCK( m_mutex ); + IUniformRandomStream *pUniformStream = m_pUniformStream ? m_pUniformStream : s_pUniformStream; + float fac,rsq,v1,v2; + + if (!m_bHaveValue) + { + // Pick 2 random #s from -1 to 1 + // Make sure they lie inside the unit circle. If they don't, try again + do + { + v1 = 2.0f * pUniformStream->RandomFloat() - 1.0f; + v2 = 2.0f * pUniformStream->RandomFloat() - 1.0f; + rsq = v1*v1 + v2*v2; + } while ((rsq > 1.0f) || (rsq == 0.0f)); + + // The box-muller transformation to get the two gaussian numbers + fac = sqrtf( -2.0f * log(rsq) / rsq ); + + // Store off one value for later use + m_flRandomValue = v1 * fac; + m_bHaveValue = true; + + return flStdDev * (v2 * fac) + flMean; + } + else + { + m_bHaveValue = false; + return flStdDev * m_flRandomValue + flMean; + } +} + + +//----------------------------------------------------------------------------- +// Creates a histogram (for testing) +//----------------------------------------------------------------------------- diff --git a/vstdlib/vcover.cpp b/vstdlib/vcover.cpp new file mode 100644 index 0000000..e4eb933 --- /dev/null +++ b/vstdlib/vcover.cpp @@ -0,0 +1,9 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "vstdlib/vcover.h" + +CVCoverage g_VCoverage; diff --git a/vstdlib/vstdlib.vpc b/vstdlib/vstdlib.vpc new file mode 100644 index 0000000..89c2214 --- /dev/null +++ b/vstdlib/vstdlib.vpc @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------------- +// VSTDLIB.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR ".." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$MacroRequired "PLATSUBDIR" + +$Configuration +{ + + $General + { + // X360 version publishes to some other directory then copies here so we need to tell VPC to track this + // or else it won't know what depends on this project. + $AdditionalOutputFiles "$SRCDIR\lib\public\$(TargetName).lib" [$X360] + } + + $Compiler + { + $PreprocessorDefinitions "$BASE;VSTDLIB_DLL_EXPORT" + $GCC_ExtraCompilerFlags "-U_FORTIFY_SOURCE" [$LINUXALL] + } + + $Linker + { + $AdditionalDependencies "$BASE odbc32.lib odbccp32.lib" [$WINDOWS] + + // pc publishes the import library directly + $ImportLibrary "$LIBPUBLIC\$(TargetName).lib" [$WINDOWS] + + // 360 publishes the import library via a post build step + $ImportLibrary "$(TargetDir)\$(TargetName).lib" [$X360] + + + // 360 will auto generate a def file for this import library + $ModuleDefinitionFile " " [$X360] + $AdditionalOptions "$BASE /AUTODEF:xbox\xbox.def" [$X360] + + // Suppress this warning using the undocumented /ignore linker switch + // tier1.lib(KeyValues.obj) : warning LNK4217: locally defined symbol _KeyValuesSystem imported in function "public: static int __cdecl KeyValues::GetSymbolForStringClassic(char const *,bool)" (?GetSymbolForStringClassic@KeyValues@@SAHPBD_N@Z) + $AdditionalOptions "$BASE /ignore:4217" [$WINDOWS] + + $SystemLibraries "iconv" [$OSXALL] + $SystemFrameworks "CoreServices" [$OSXALL] + $GCC_ExtraLinkerFlags "-all_load" [$OSXALL] + + $ImportLibrary "$LIBPUBLIC\$_IMPLIB_PREFIX$OUTBINNAME$_IMPLIB_EXT" [$POSIX] + $OutputFile "$(OBJ_DIR)/$_IMPLIB_PREFIX$OUTBINNAME$_IMPLIB_EXT" [$POSIX] + } + + $PreLinkEvent [$WINDOWS] + { + $CommandLine "call $SRCDIR\vpc_scripts\valve_p4_edit.cmd $LIBPUBLIC\$(TargetName).lib $SRCDIR" "\n" \ + "$BASE" + } + + $PreLinkEvent [$X360] + { + // Run a pre-link event to clean the .def file from the last link + $CommandLine "if exist xbox\xbox.def del xbox\xbox.def" "\n" \ + "$BASE" + } + + $PostBuildEvent [$X360] + { + // Run a post build event to validate the .def file was correctly generated + $CommandLine "perl $SRCDIR\devtools\bin\make360def.pl -checkauto xbox\xbox.def" "\n" \ + "if exist $(TargetDir)$(TargetName).lib copy $(TargetDir)$(TargetName).lib $SRCDIR\lib\public\$(TargetName).lib" "\n" \ + "$BASE" + } + + $General [$POSIX] + { + $GameOutputFile "$OUTBINDIR/$_IMPLIB_DLL_PREFIX$OUTBINNAME$_DLL_EXT" + } +} + + + +$Project "vstdlib" +{ + $Folder "Source Files" + { + $File "xbox\___FirstModule.cpp" [$X360] + $File "GetStackPtr64.masm" [$WIN64] + { + $Configuration + { + $CustomBuildStep + { + // General + $CommandLine "$QUOTE$(VCInstallDir)bin\x86_amd64\ml64.exe$QUOTE /nologo /c /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" + $Description "Compiling GetStackPtr64.masm" + $Outputs "$(IntDir)\$(InputName).obj" + } + } + } + $File "coroutine_win64.masm" [$WIN64] + { + $Configuration + { + $CustomBuildStep + { + // General + $CommandLine "$QUOTE$(VCInstallDir)bin\x86_amd64\ml64.exe$QUOTE /c /Fo$QUOTE$(IntDir)\$(InputName).obj$QUOTE $QUOTE$(InputPath)$QUOTE" + $Description "Compiling coroutine_win64.masm" + $Outputs "$(IntDir)\$(InputName).obj" + } + } + } + + $File "coroutine.cpp" [!$X360 && !$OSXALL] + { + $Configuration + { + $Compiler + { + $BasicRuntimeChecks "Default" + } + } + } + $File "cvar.cpp" + $File "jobthread.cpp" + $File "KeyValuesSystem.cpp" + $File "osversion.cpp" + $File "processutils.cpp" [$WINDOWS] + $File "random.cpp" + $File "vcover.cpp" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\vstdlib\cvar.h" + $File "$SRCDIR\public\vstdlib\coroutine.h" + $File "$SRCDIR\public\vstdlib\jobthread.h" + $File "$SRCDIR\public\vstdlib\IKeyValuesSystem.h" + $File "$SRCDIR\public\vstdlib\iprocessutils.h" + $File "$SRCDIR\public\tier1\mempool.h" + $File "$SRCDIR\public\vstdlib\osversion.h" + $File "$SRCDIR\public\vstdlib\random.h" + $File "$SRCDIR\public\vstdlib\vcover.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + } + + $Folder "Link Libraries" + { + -$ImpLib vstdlib + $Lib "coroutine_osx" [$OSXALL] + } +} + diff --git a/vstdlib/xbox/___FirstModule.cpp b/vstdlib/xbox/___FirstModule.cpp new file mode 100644 index 0000000..f37a6e9 --- /dev/null +++ b/vstdlib/xbox/___FirstModule.cpp @@ -0,0 +1,19 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// MUST BE THE FIRST MODULE IN THE LINK PROCESS TO ACHIEVE @1 +// +// This is a 360 specific trick to force this import library and the new 360 +// link option /AUTODEF to put CreateInterface at @1 (360 lacks named exports) and +// first in sequence. Otherwise, the valve interface techique that does a +// GetProcAddress( @1 ) gets the wrong function pointer. All other exported +// functions can appear in any order, but the oridnals should be autogened sequential. +//===========================================================================// + +#include "tier1/tier1.h" + +// Should be the first function that the linker 'sees' as an export +void* CreateInterfaceThunk( const char *pName, int *pReturnCode ) +{ + // descend into the real function + return CreateInterface( pName, pReturnCode ); +}