You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1426 lines
32 KiB
1426 lines
32 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
// |
|
//=============================================================================// |
|
|
|
#include "host.h" |
|
#include <ctype.h> |
|
#include "draw.h" |
|
#include "zone.h" |
|
#include "sys.h" |
|
#include <edict.h> |
|
#include <coordsize.h> |
|
#include <characterset.h> |
|
#include <bitbuf.h> |
|
#include "common.h" |
|
#ifdef OSX |
|
#include <malloc/malloc.h> |
|
#else |
|
#include <malloc.h> |
|
#endif |
|
#include "traceinit.h" |
|
#include <filesystem.h> |
|
#include "filesystem_engine.h" |
|
#include <convar.h> |
|
#include "gl_matsysiface.h" |
|
#include "filesystem_init.h" |
|
#include <materialsystem/imaterialsystemhardwareconfig.h> |
|
#include <tier0/icommandline.h> |
|
#include <vstdlib/random.h> |
|
#include "sys_dll.h" |
|
#include "datacache/idatacache.h" |
|
#include "matchmaking.h" |
|
#include "tier1/KeyValues.h" |
|
#include "vgui_baseui_interface.h" |
|
#include "tier2/tier2.h" |
|
#include "language.h" |
|
#ifndef SWDS |
|
#include "cl_steamauth.h" |
|
#endif |
|
#include "tier3/tier3.h" |
|
#include <vgui/ILocalize.h> |
|
#include "tier1/lzss.h" |
|
#include "tier1/snappy.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// Things in other C files. |
|
#define MAX_LOG_DIRECTORIES 10000 |
|
|
|
bool com_ignorecolons = false; |
|
|
|
// wordbreak parsing set |
|
static characterset_t g_BreakSet, g_BreakSetIncludingColons; |
|
|
|
#define COM_TOKEN_MAX_LENGTH 1024 |
|
char com_token[COM_TOKEN_MAX_LENGTH]; |
|
|
|
/* |
|
All of Quake's data access is through a hierarchical file system, but the contents of |
|
the file system can be transparently merged from several sources. |
|
|
|
The "base directory" is the path to the directory holding the quake.exe and all |
|
game directories. The sys_* files pass this to host_init in engineparms->basedir. |
|
This can be overridden with the "-basedir" command line parm to allow code |
|
debugging in a different directory. The base directory is |
|
only used during filesystem initialization. |
|
|
|
The "game directory" is the first tree on the search path and directory |
|
that all generated files (savegames, screenshots, demos, config files) will |
|
be saved to. This can be overridden with the "-game" command line parameter. |
|
The game directory can never be changed while quake is executing. |
|
This is a precacution against having a malicious server instruct clients |
|
to write files over areas they shouldn't. |
|
|
|
The "cache directory" is only used during development to save network bandwidth, |
|
especially over ISDN / T1 lines. If there is a cache directory |
|
specified, when a file is found by the normal search path, it will be mirrored |
|
into the cache directory, then opened there. |
|
|
|
FIXME: |
|
The file "parms.txt" will be read out of the game directory and appended to the |
|
current command line arguments to allow different games to initialize startup |
|
parms differently. This could be used to add a "-sspeed 22050" for the high |
|
quality sound edition. Because they are added at the end, they will not override |
|
an explicit setting on the original command line. |
|
*/ |
|
|
|
/* |
|
============================== |
|
COM_ExplainDisconnection |
|
|
|
============================== |
|
*/ |
|
void COM_ExplainDisconnection( bool bPrint, const char *fmt, ... ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
g_pMatchmaking->SessionNotification( SESSION_NOTIFY_LOST_SERVER ); |
|
} |
|
else |
|
{ |
|
va_list argptr; |
|
char string[1024]; |
|
|
|
va_start (argptr, fmt); |
|
Q_vsnprintf(string, sizeof( string ), fmt,argptr); |
|
va_end (argptr); |
|
|
|
Q_strncpy( gszDisconnectReason, string, 256 ); |
|
gfExtendedError = true; |
|
} |
|
|
|
if ( bPrint ) |
|
{ |
|
if ( gszDisconnectReason[0] == '#' ) |
|
{ |
|
wchar_t formatStr[256]; |
|
const wchar_t *wpchReason = g_pVGuiLocalize ? g_pVGuiLocalize->Find(gszDisconnectReason) : NULL; |
|
if ( wpchReason ) |
|
{ |
|
wcsncpy(formatStr, wpchReason, sizeof( formatStr ) / sizeof( wchar_t ) ); |
|
|
|
char conStr[256]; |
|
g_pVGuiLocalize->ConvertUnicodeToANSI(formatStr, conStr, sizeof( conStr )); |
|
ConMsg( "%s\n", conStr ); |
|
} |
|
else |
|
ConMsg( "%s\n", gszDisconnectReason ); |
|
} |
|
else |
|
{ |
|
ConMsg( "%s\n", gszDisconnectReason ); |
|
} |
|
} |
|
} |
|
|
|
/* |
|
============================== |
|
COM_ExtendedExplainDisconnection |
|
|
|
============================== |
|
*/ |
|
void COM_ExtendedExplainDisconnection( bool bPrint, const char *fmt, ... ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
g_pMatchmaking->SessionNotification( SESSION_NOTIFY_LOST_SERVER ); |
|
} |
|
else |
|
{ |
|
va_list argptr; |
|
char string[1024]; |
|
|
|
va_start (argptr, fmt); |
|
Q_vsnprintf(string, sizeof( string ), fmt,argptr); |
|
va_end (argptr); |
|
|
|
Q_strncpy( gszExtendedDisconnectReason, string, 256 ); |
|
} |
|
|
|
if ( bPrint ) |
|
{ |
|
ConMsg( "%s\n", gszExtendedDisconnectReason ); |
|
} |
|
} |
|
|
|
/* |
|
============== |
|
COM_Parse |
|
|
|
Parse a token out of a string |
|
============== |
|
*/ |
|
const char *COM_Parse (const char *data) |
|
{ |
|
unsigned char c; |
|
int len; |
|
characterset_t *breaks; |
|
|
|
breaks = &g_BreakSetIncludingColons; |
|
if ( com_ignorecolons ) |
|
breaks = &g_BreakSet; |
|
|
|
len = 0; |
|
com_token[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) |
|
{ |
|
com_token[len] = 0; |
|
return data; |
|
} |
|
com_token[len] = c; |
|
len++; |
|
} |
|
} |
|
|
|
// parse single characters |
|
if ( IN_CHARACTERSET( *breaks, c ) ) |
|
{ |
|
com_token[len] = c; |
|
len++; |
|
com_token[len] = 0; |
|
return data+1; |
|
} |
|
|
|
// parse a regular word |
|
do |
|
{ |
|
com_token[len] = c; |
|
data++; |
|
len++; |
|
c = *data; |
|
if ( IN_CHARACTERSET( *breaks, c ) ) |
|
break; |
|
} while (c>32); |
|
|
|
com_token[len] = 0; |
|
return data; |
|
} |
|
|
|
/* |
|
============== |
|
COM_AddNoise |
|
|
|
Changes n random bits in a data block |
|
============== |
|
*/ |
|
void COM_AddNoise( unsigned char *data, int length, int number ) |
|
{ |
|
for ( int i = 0; i < number; i++ ) |
|
{ |
|
int randomByte = RandomInt( 0, length-1 ); |
|
int randomBit = RandomInt( 0, 7 ); |
|
|
|
// get original data |
|
unsigned char dataByte = data[randomByte]; |
|
|
|
// flip bit |
|
if ( dataByte & randomBit ) |
|
{ |
|
dataByte &= ~randomBit; |
|
} |
|
else |
|
{ |
|
dataByte |= randomBit; |
|
} |
|
|
|
// write back |
|
data[randomByte] = dataByte; |
|
} |
|
} |
|
|
|
/* |
|
============== |
|
COM_Parse_Line |
|
|
|
Parse a line out of a string |
|
============== |
|
*/ |
|
const char *COM_ParseLine (const char *data) |
|
{ |
|
int c; |
|
int len; |
|
|
|
len = 0; |
|
com_token[0] = 0; |
|
|
|
if (!data) |
|
return NULL; |
|
|
|
c = *data; |
|
|
|
// parse a line out of the data |
|
do |
|
{ |
|
com_token[len] = c; |
|
data++; |
|
len++; |
|
c = *data; |
|
} while ( ( c>=' ' || c < 0 || c == '\t' ) && ( len < COM_TOKEN_MAX_LENGTH - 1 ) ); |
|
|
|
com_token[len] = 0; |
|
|
|
if (c==0) // end of file |
|
return NULL; |
|
|
|
// eat whitespace (LF,CR,etc.) at the end of this line |
|
while ( (c = *data) < ' ' ) |
|
{ |
|
if (c == 0) |
|
return NULL; // end of file; |
|
data++; |
|
} |
|
|
|
return data; |
|
} |
|
|
|
/* |
|
============== |
|
COM_TokenWaiting |
|
|
|
Returns 1 if additional data is waiting to be processed on this line |
|
============== |
|
*/ |
|
int COM_TokenWaiting( const char *buffer ) |
|
{ |
|
const char *p; |
|
|
|
p = buffer; |
|
while ( *p && *p!='\n') |
|
{ |
|
if ( !V_isspace( *p ) || V_isalnum( *p ) ) |
|
return 1; |
|
|
|
p++; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
/* |
|
============ |
|
tmpstr512 |
|
|
|
rotates through a bunch of string buffers of 512 bytes each |
|
============ |
|
*/ |
|
char *tmpstr512() |
|
{ |
|
static char string[32][512]; |
|
static int curstring = 0; |
|
curstring = ( curstring + 1 ) & 31; |
|
return string[curstring]; |
|
} |
|
|
|
/* |
|
============ |
|
va |
|
|
|
does a varargs printf into a temp buffer, so I don't need to have |
|
varargs versions of all text functions. |
|
============ |
|
*/ |
|
char *va( const char *format, ... ) |
|
{ |
|
char* outbuf = tmpstr512(); |
|
va_list argptr; |
|
va_start (argptr, format); |
|
Q_vsnprintf( outbuf, 512, format, argptr ); |
|
va_end (argptr); |
|
return outbuf; |
|
} |
|
|
|
/* |
|
============ |
|
vstr |
|
|
|
prints a vector into a temporary string |
|
bufffer. |
|
============ |
|
*/ |
|
const char *vstr(Vector& v) |
|
{ |
|
char* outbuf = tmpstr512(); |
|
Q_snprintf(outbuf, 512, "%.2f %.2f %.2f", v[0], v[1], v[2]); |
|
return outbuf; |
|
} |
|
|
|
char com_basedir[MAX_OSPATH]; |
|
char com_gamedir[MAX_OSPATH]; |
|
|
|
/* |
|
================== |
|
CL_CheckGameDirectory |
|
|
|
Client side game directory change. |
|
================== |
|
*/ |
|
bool COM_CheckGameDirectory( const char *gamedir ) |
|
{ |
|
// Switch game directories if needed, or abort if it's not good. |
|
char szGD[ MAX_OSPATH ]; |
|
|
|
if ( !gamedir || !gamedir[0] ) |
|
{ |
|
ConMsg( "Server didn't specify a gamedir, assuming no change\n" ); |
|
return true; |
|
} |
|
|
|
// Rip out the current gamedir. |
|
Q_FileBase( com_gamedir, szGD, sizeof( szGD ) ); |
|
|
|
if ( Q_stricmp( szGD, gamedir ) ) |
|
{ |
|
// Changing game directories without restarting is not permitted any more |
|
ConMsg( "COM_CheckGameDirectory: game directories don't match (%s / %s)\n", szGD, gamedir ); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds the file in the search path. |
|
// Input : *filename - |
|
// *file - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int COM_FindFile( const char *filename, FileHandle_t *file ) |
|
{ |
|
Assert( file ); |
|
int filesize = -1; |
|
*file = g_pFileSystem->Open( filename, "rb" ); |
|
if ( *file ) |
|
{ |
|
filesize = g_pFileSystem->Size( *file ); |
|
} |
|
return filesize; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *filename - |
|
// *file - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int COM_OpenFile( const char *filename, FileHandle_t *file ) |
|
{ |
|
return COM_FindFile( (char *)filename, file ); |
|
} |
|
|
|
/* |
|
============ |
|
COM_WriteFile |
|
|
|
The filename will be prefixed by the current game directory |
|
============ |
|
*/ |
|
void COM_WriteFile (const char *filename, void *data, int len) |
|
{ |
|
FileHandle_t handle; |
|
|
|
int nameLen = strlen( filename ) + 2; |
|
char *pName = ( char * )_alloca( nameLen ); |
|
|
|
Q_snprintf( pName, nameLen, "%s", filename); |
|
|
|
Q_FixSlashes( pName ); |
|
COM_CreatePath( pName ); |
|
|
|
handle = g_pFileSystem->Open( pName, "wb" ); |
|
if ( !handle ) |
|
{ |
|
Warning ("COM_WriteFile: failed on %s\n", pName); |
|
return; |
|
} |
|
|
|
g_pFileSystem->Write( data, len, handle ); |
|
g_pFileSystem->Close( handle ); |
|
} |
|
|
|
/* |
|
============ |
|
COM_CreatePath |
|
|
|
Only used for CopyFile |
|
============ |
|
*/ |
|
void COM_CreatePath (const char *path) |
|
{ |
|
char temppath[1024]; |
|
Q_strncpy(temppath, path, sizeof(temppath)); |
|
Q_StripFilename( temppath ); |
|
|
|
Sys_mkdir( temppath ); |
|
} |
|
|
|
|
|
/* |
|
=========== |
|
COM_CopyFile |
|
|
|
Copies a file from pSourcePath to pDestPath. |
|
=========== |
|
*/ |
|
bool COM_CopyFile ( const char *pSourcePath, const char *pDestPath ) |
|
{ |
|
if ( IsX360() ) |
|
return false; |
|
|
|
int remaining, count; |
|
char buf[4096]; |
|
FileHandle_t in, out; |
|
|
|
in = g_pFileSystem->Open( pSourcePath, "rb" ); |
|
|
|
AssertMsg( in, "COM_CopyFile(): Input file failed to open" ); |
|
|
|
if ( in == FILESYSTEM_INVALID_HANDLE ) |
|
return false; |
|
|
|
// create directories up to the cache file |
|
COM_CreatePath( pDestPath ); |
|
|
|
out = g_pFileSystem->Open( pDestPath, "wb" ); |
|
|
|
AssertMsg( out, "COM_CopyFile(): Output file failed to open" ); |
|
|
|
if ( out == FILESYSTEM_INVALID_HANDLE ) |
|
{ |
|
g_pFileSystem->Close( in ); |
|
return false; |
|
} |
|
|
|
remaining = g_pFileSystem->Size( in ); |
|
while ( remaining > 0 ) |
|
{ |
|
if (remaining < sizeof(buf)) |
|
{ |
|
count = remaining; |
|
} |
|
else |
|
{ |
|
count = sizeof(buf); |
|
} |
|
g_pFileSystem->Read( buf, count, in ); |
|
g_pFileSystem->Write( buf, count, out ); |
|
remaining -= count; |
|
} |
|
|
|
g_pFileSystem->Close( in ); |
|
g_pFileSystem->Close( out ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
/* |
|
=========== |
|
COM_ExpandFilename |
|
|
|
Finds the file in the search path, copies over the name with the full path name. |
|
This doesn't search in the pak file. |
|
=========== |
|
*/ |
|
int COM_ExpandFilename( char *filename, int maxlength ) |
|
{ |
|
char expanded[MAX_OSPATH]; |
|
|
|
if ( g_pFileSystem->GetLocalPath( filename, expanded, sizeof(expanded) ) != NULL ) |
|
{ |
|
Q_strncpy( filename, expanded, maxlength ); |
|
return 1; |
|
} |
|
|
|
if ( filename && filename[0] != '*' ) |
|
{ |
|
Warning ("COM_ExpandFilename: can't find %s\n", filename); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
=========== |
|
COM_FileSize |
|
|
|
Returns the size of the file only. |
|
=========== |
|
*/ |
|
int COM_FileSize (const char *filename) |
|
{ |
|
return g_pFileSystem->Size(filename); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Close file handle |
|
// Input : hFile - |
|
//----------------------------------------------------------------------------- |
|
void COM_CloseFile( FileHandle_t hFile ) |
|
{ |
|
g_pFileSystem->Close( hFile ); |
|
} |
|
|
|
|
|
/* |
|
============ |
|
COM_LoadFile |
|
|
|
Filename are reletive to the quake directory. |
|
Allways appends a 0 byte. |
|
============ |
|
*/ |
|
cache_user_t *loadcache; |
|
byte *loadbuf; |
|
int loadsize; |
|
byte *COM_LoadFile (const char *path, int usehunk, int *pLength) |
|
{ |
|
FileHandle_t hFile; |
|
byte *buf = NULL; |
|
char base[128]; |
|
int len; |
|
|
|
if (pLength) |
|
{ |
|
*pLength = 0; |
|
} |
|
|
|
// look for it in the filesystem or pack files |
|
len = COM_OpenFile( path, &hFile ); |
|
if ( !hFile ) |
|
{ |
|
return NULL; |
|
} |
|
|
|
// Extract the filename base name for hunk tag |
|
Q_FileBase( path, base, sizeof( base ) ); |
|
|
|
unsigned bufSize = len + 1; |
|
if ( IsX360() ) |
|
{ |
|
bufSize = g_pFileSystem->GetOptimalReadSize( hFile, bufSize ); // align to sector |
|
} |
|
|
|
switch ( usehunk ) |
|
{ |
|
case 1: |
|
buf = (byte *)Hunk_AllocName (bufSize, base); |
|
break; |
|
case 2: |
|
AssertMsg( 0, "Temp alloc no longer supported\n" ); |
|
break; |
|
case 3: |
|
AssertMsg( 0, "Cache alloc no longer supported\n" ); |
|
break; |
|
case 4: |
|
{ |
|
if (len+1 > loadsize) |
|
buf = (byte *)malloc(bufSize); |
|
else |
|
buf = loadbuf; |
|
} |
|
break; |
|
case 5: |
|
buf = (byte *)malloc(bufSize); // YWB: FIXME, this is evil. |
|
break; |
|
default: |
|
Sys_Error ("COM_LoadFile: bad usehunk"); |
|
} |
|
|
|
if ( !buf ) |
|
{ |
|
Sys_Error ("COM_LoadFile: not enough space for %s", path); |
|
COM_CloseFile(hFile); // exit here to prevent fault on oom (kdb) |
|
return NULL; |
|
} |
|
|
|
g_pFileSystem->ReadEx( buf, bufSize, len, hFile ); |
|
COM_CloseFile( hFile ); |
|
|
|
((byte *)buf)[ len ] = 0; |
|
|
|
if ( pLength ) |
|
{ |
|
*pLength = len; |
|
} |
|
return buf; |
|
} |
|
|
|
/* |
|
=============== |
|
COM_CopyFileChunk |
|
|
|
=============== |
|
*/ |
|
void COM_CopyFileChunk( FileHandle_t dst, FileHandle_t src, int nSize ) |
|
{ |
|
int copysize = nSize; |
|
char copybuf[COM_COPY_CHUNK_SIZE]; |
|
|
|
while (copysize > COM_COPY_CHUNK_SIZE) |
|
{ |
|
g_pFileSystem->Read ( copybuf, COM_COPY_CHUNK_SIZE, src ); |
|
g_pFileSystem->Write( copybuf, COM_COPY_CHUNK_SIZE, dst ); |
|
copysize -= COM_COPY_CHUNK_SIZE; |
|
} |
|
|
|
g_pFileSystem->Read ( copybuf, copysize, src ); |
|
g_pFileSystem->Write( copybuf, copysize, dst ); |
|
|
|
g_pFileSystem->Flush ( src ); |
|
g_pFileSystem->Flush ( dst ); |
|
} |
|
|
|
// uses malloc if larger than bufsize |
|
byte *COM_LoadStackFile (const char *path, void *buffer, int bufsize, int& filesize ) |
|
{ |
|
byte *buf; |
|
loadbuf = (byte *)buffer; |
|
loadsize = bufsize; |
|
buf = COM_LoadFile (path, 4, &filesize ); |
|
return buf; |
|
} |
|
|
|
void COM_ShutdownFileSystem( void ) |
|
{ |
|
} |
|
|
|
/* |
|
================ |
|
COM_Shutdown |
|
|
|
Remove the searchpaths |
|
================ |
|
*/ |
|
void COM_Shutdown( void ) |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: allocates memory and copys source text |
|
// Input : *in - |
|
// Output : char *CopyString |
|
//----------------------------------------------------------------------------- |
|
char *COM_StringCopy(const char *in) |
|
{ |
|
int len = Q_strlen(in)+1; |
|
char *out = (char *)new char[ len ]; |
|
Q_strncpy (out, in, len ); |
|
return out; |
|
} |
|
|
|
void COM_StringFree(const char *in) |
|
{ |
|
delete [] in; |
|
} |
|
|
|
|
|
void COM_SetupLogDir( const char *mapname ) |
|
{ |
|
char gameDir[MAX_OSPATH]; |
|
COM_GetGameDir( gameDir, sizeof( gameDir ) ); |
|
|
|
// Blat out the all directories in the LOGDIR path |
|
g_pFileSystem->RemoveSearchPath( NULL, "LOGDIR" ); |
|
|
|
// set the log directory |
|
if ( mapname && CommandLine()->FindParm("-uselogdir") ) |
|
{ |
|
int i; |
|
char sRelativeLogDir[MAX_PATH]; |
|
for ( i = 0; i < MAX_LOG_DIRECTORIES; i++ ) |
|
{ |
|
Q_snprintf( sRelativeLogDir, sizeof( sRelativeLogDir ), "logs/%s/%04i", mapname, i ); |
|
if ( !g_pFileSystem->IsDirectory( sRelativeLogDir, "GAME" ) ) |
|
break; |
|
} |
|
|
|
// Loop at max |
|
if ( i == MAX_LOG_DIRECTORIES ) |
|
{ |
|
i = 0; |
|
Q_snprintf( sRelativeLogDir, sizeof( sRelativeLogDir ), "logs/%s/%04i", mapname, i ); |
|
} |
|
|
|
// Make sure the directories we need exist. |
|
g_pFileSystem->CreateDirHierarchy( sRelativeLogDir, "GAME" ); |
|
|
|
{ |
|
static bool pathsetup = false; |
|
|
|
if ( !pathsetup ) |
|
{ |
|
pathsetup = true; |
|
|
|
// Set the search path |
|
char sLogDir[MAX_PATH]; |
|
Q_snprintf( sLogDir, sizeof( sLogDir ), "%s/%s", gameDir, sRelativeLogDir ); |
|
g_pFileSystem->AddSearchPath( sLogDir, "LOGDIR" ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Default to the base game directory for logs. |
|
g_pFileSystem->AddSearchPath( gameDir, "LOGDIR" ); |
|
} |
|
} |
|
|
|
/* |
|
================ |
|
COM_GetModDirectory - return the final directory in the game dir (i.e "cstrike", "hl2", rather than c:\blah\cstrike ) |
|
================ |
|
*/ |
|
const char *COM_GetModDirectory() |
|
{ |
|
static char modDir[MAX_PATH]; |
|
if ( Q_strlen( modDir ) == 0 ) |
|
{ |
|
const char *gamedir = CommandLine()->ParmValue("-game", CommandLine()->ParmValue( "-defaultgamedir", "hl2" ) ); |
|
Q_strncpy( modDir, gamedir, sizeof(modDir) ); |
|
if ( strchr( modDir, '/' ) || strchr( modDir, '\\' ) ) |
|
{ |
|
Q_StripLastDir( modDir, sizeof(modDir) ); |
|
int dirlen = Q_strlen( modDir ); |
|
Q_strncpy( modDir, gamedir + dirlen, sizeof(modDir) - dirlen ); |
|
} |
|
} |
|
|
|
return modDir; |
|
} |
|
|
|
|
|
/* |
|
================ |
|
Return if we should load content from the _hd folder for this mod |
|
This logic needs to match with the gameui/OptionsSubVideo.cpp code |
|
================ |
|
*/ |
|
bool BLoadHDContent( const char *pchModDir, const char *pchBaseDir ) |
|
{ |
|
char szModSteamInfPath[ 1024 ]; |
|
V_ComposeFileName( pchModDir, "game_hd.txt", szModSteamInfPath, sizeof( szModSteamInfPath ) ); |
|
char szFullPath[ 1024 ]; |
|
V_MakeAbsolutePath( szFullPath, sizeof( szFullPath ), szModSteamInfPath, pchBaseDir ); |
|
|
|
FILE *fp = fopen( szFullPath, "rb" ); |
|
if ( fp ) |
|
{ |
|
fclose(fp); |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
extern void Host_CheckGore( void ); |
|
|
|
/* |
|
================ |
|
COM_InitFilesystem |
|
================ |
|
*/ |
|
void COM_InitFilesystem( const char *pFullModPath ) |
|
{ |
|
CFSSearchPathsInit initInfo; |
|
|
|
#ifndef SWDS |
|
if ( IsPC() ) |
|
{ |
|
static char language[128]; |
|
language[0] = 0; |
|
|
|
// There are two language at play here. The Audio language which is controled by the |
|
// properties on the game itself in Steam (at least for now). And the language Steam is set to. |
|
// Under Windows the text in the game is controled by the language Steam is set in, but the audio |
|
// is controled by the language set in the game's properties which we can get from Steam3Client |
|
|
|
// A command line override for audio language has also been added. |
|
// -audiolanguage <language> |
|
// User must have the .vpk files for the language installed though in order to use the command line switch |
|
|
|
if ( Steam3Client().SteamApps() ) |
|
{ |
|
// use -audiolanguage command line to override audio language, otherwise take language from steam |
|
Q_strncpy(language, CommandLine()->ParmValue("-audiolanguage", Steam3Client().SteamApps()->GetCurrentGameLanguage()), sizeof( language ) - 1); |
|
} |
|
else |
|
{ |
|
char *szLang = getenv("LANG"); |
|
|
|
// still allow command line override even when not running steam |
|
if (CommandLine()->CheckParm("-audiolanguage")) |
|
{ |
|
Q_strncpy(language, CommandLine()->ParmValue("-audiolanguage", "english"), sizeof( language ) - 1); |
|
} |
|
else if( szLang ) |
|
{ |
|
ELanguage lang = PchLanguageICUCodeToELanguage(szLang, k_Lang_English); |
|
const char *szShortLang = GetLanguageShortName(lang); |
|
if( Q_strncmp(szShortLang, "none", 4) != 0 ) |
|
Q_strncpy(language, szShortLang, sizeof( language ) - 1); |
|
} |
|
} |
|
|
|
if ( ( Q_strlen(language) > 0 ) && ( Q_stricmp(language, "english") ) ) |
|
{ |
|
initInfo.m_pLanguage = language; |
|
} |
|
} |
|
#endif |
|
|
|
initInfo.m_pFileSystem = g_pFileSystem; |
|
initInfo.m_pDirectoryName = pFullModPath; |
|
if ( !initInfo.m_pDirectoryName ) |
|
{ |
|
initInfo.m_pDirectoryName = GetCurrentGame(); |
|
} |
|
|
|
Host_CheckGore(); |
|
|
|
initInfo.m_bLowViolence = g_bLowViolence; |
|
initInfo.m_bMountHDContent = BLoadHDContent( initInfo.m_pDirectoryName, GetBaseDirectory() ); |
|
|
|
// Load gameinfo.txt and setup all the search paths, just like the tools do. |
|
FileSystem_LoadSearchPaths( initInfo ); |
|
|
|
// The mod path becomes com_gamedir. |
|
Q_MakeAbsolutePath( com_gamedir, sizeof( com_gamedir ), initInfo.m_ModPath ); |
|
|
|
// Set com_basedir. |
|
Q_strncpy ( com_basedir, GetBaseDirectory(), sizeof( com_basedir ) ); // the "root" directory where hl2.exe is |
|
Q_strlower( com_basedir ); |
|
Q_FixSlashes( com_basedir ); |
|
|
|
#if !defined( SWDS ) && !defined( DEDICATED ) |
|
EngineVGui()->SetVGUIDirectories(); |
|
#endif |
|
|
|
// Set LOGDIR to be something reasonable |
|
COM_SetupLogDir( NULL ); |
|
|
|
// g_pFileSystem->PrintSearchPaths(); |
|
|
|
} |
|
|
|
const char *COM_DXLevelToString( int dxlevel ) |
|
{ |
|
bool bHalfPrecision = false; |
|
|
|
const char *pShaderDLLName = g_pMaterialSystemHardwareConfig->GetShaderDLLName(); |
|
if( pShaderDLLName && Q_stristr( pShaderDLLName, "nvfx" ) ) |
|
{ |
|
bHalfPrecision = true; |
|
} |
|
|
|
if( CommandLine()->CheckParm( "-dxlevel" ) ) |
|
{ |
|
switch( dxlevel ) |
|
{ |
|
case 0: |
|
return "default"; |
|
case 60: |
|
return "6.0"; |
|
case 70: |
|
return "7.0"; |
|
case 80: |
|
return "8.0"; |
|
case 81: |
|
return "8.1"; |
|
case 82: |
|
if( bHalfPrecision ) |
|
{ |
|
return "8.1 with some 9.0 (half-precision)"; |
|
} |
|
else |
|
{ |
|
return "8.1 with some 9.0 (full-precision)"; |
|
} |
|
case 90: |
|
if( bHalfPrecision ) |
|
{ |
|
return "9.0 (half-precision)"; |
|
} |
|
else |
|
{ |
|
return "9.0 (full-precision)"; |
|
} |
|
default: |
|
return "UNKNOWN"; |
|
} |
|
} |
|
else |
|
{ |
|
switch( dxlevel ) |
|
{ |
|
case 60: |
|
return "gamemode - 6.0"; |
|
case 70: |
|
return "gamemode - 7.0"; |
|
case 80: |
|
return "gamemode - 8.0"; |
|
case 81: |
|
return "gamemode - 8.1"; |
|
case 82: |
|
if( bHalfPrecision ) |
|
{ |
|
return "gamemode - 8.1 with some 9.0 (half-precision)"; |
|
} |
|
else |
|
{ |
|
return "gamemode - 8.1 with some 9.0 (full-precision)"; |
|
} |
|
case 90: |
|
if( bHalfPrecision ) |
|
{ |
|
return "gamemode - 9.0 (half-precision)"; |
|
} |
|
else |
|
{ |
|
return "gamemode - 9.0 (full-precision)"; |
|
} |
|
default: |
|
return "gamemode"; |
|
} |
|
} |
|
} |
|
|
|
const char *COM_FormatSeconds( int seconds ) |
|
{ |
|
static char string[64]; |
|
|
|
int hours = 0; |
|
int minutes = seconds / 60; |
|
|
|
if ( minutes > 0 ) |
|
{ |
|
seconds -= (minutes * 60); |
|
hours = minutes / 60; |
|
|
|
if ( hours > 0 ) |
|
{ |
|
minutes -= (hours * 60); |
|
} |
|
} |
|
|
|
if ( hours > 0 ) |
|
{ |
|
Q_snprintf( string, sizeof(string), "%2i:%02i:%02i", hours, minutes, seconds ); |
|
} |
|
else |
|
{ |
|
Q_snprintf( string, sizeof(string), "%02i:%02i", minutes, seconds ); |
|
} |
|
|
|
return string; |
|
} |
|
|
|
// Non-VarArgs version |
|
void COM_LogString( char const *pchFile, char const *pchString ) |
|
{ |
|
if ( !g_pFileSystem ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
FileHandle_t fp; |
|
const char *pfilename; |
|
|
|
if ( !pchFile ) |
|
{ |
|
pfilename = "hllog.txt"; |
|
} |
|
else |
|
{ |
|
pfilename = pchFile; |
|
} |
|
|
|
fp = g_pFileSystem->Open( pfilename, "a+t"); |
|
if (fp) |
|
{ |
|
g_pFileSystem->Write( pchString, strlen( pchString), fp ); |
|
g_pFileSystem->Close(fp); |
|
} |
|
} |
|
|
|
void COM_Log( const char *pszFile, const char *fmt, ...) |
|
{ |
|
if ( !g_pFileSystem ) |
|
{ |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
va_list argptr; |
|
char string[8192]; |
|
|
|
va_start (argptr,fmt); |
|
Q_vsnprintf(string, sizeof( string ), fmt,argptr); |
|
va_end (argptr); |
|
|
|
COM_LogString( pszFile, string ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *filename1 - |
|
// *filename2 - |
|
// *iCompare - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int COM_CompareFileTime(const char *filename1, const char *filename2, int *iCompare) |
|
{ |
|
int bRet = 0; |
|
if ( iCompare ) |
|
{ |
|
*iCompare = 0; |
|
} |
|
|
|
if (filename1 && filename2) |
|
{ |
|
long ft1 = g_pFileSystem->GetFileTime( filename1 ); |
|
long ft2 = g_pFileSystem->GetFileTime( filename2 ); |
|
|
|
if ( iCompare ) |
|
{ |
|
*iCompare = Sys_CompareFileTime( ft1, ft2 ); |
|
} |
|
bRet = 1; |
|
} |
|
|
|
return bRet; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *szGameDir - |
|
//----------------------------------------------------------------------------- |
|
void COM_GetGameDir(char *szGameDir, int maxlen) |
|
{ |
|
if (!szGameDir) return; |
|
Q_strncpy(szGameDir, com_gamedir, maxlen ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Parse a token from a file stream |
|
// Input : *data - |
|
// *token - |
|
// Output : char |
|
//----------------------------------------------------------------------------- |
|
const char *COM_ParseFile(const char *data, char *token, int maxtoken ) |
|
{ |
|
const char *return_data = COM_Parse(data); |
|
Q_strncpy(token, com_token, maxtoken); |
|
return return_data; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : void COM_Init |
|
//----------------------------------------------------------------------------- |
|
void COM_Init ( void ) |
|
{ |
|
CharacterSetBuild( &g_BreakSet, "{}()'" ); |
|
CharacterSetBuild( &g_BreakSetIncludingColons, "{}()':" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool COM_IsValidPath( const char *pszFilename ) |
|
{ |
|
if ( !pszFilename ) |
|
{ |
|
return false; |
|
} |
|
|
|
if ( Q_strlen( pszFilename ) <= 0 || |
|
Q_strstr( pszFilename, "\\\\" ) || // to protect network paths |
|
Q_strstr( pszFilename, ":" ) || // to protect absolute paths |
|
Q_strstr( pszFilename, ".." ) || // to protect relative paths |
|
Q_strstr( pszFilename, "\n" ) || // CFileSystem_Stdio::FS_fopen doesn't allow this |
|
Q_strstr( pszFilename, "\r" ) ) // CFileSystem_Stdio::FS_fopen doesn't allow this |
|
{ |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool COM_IsValidLogFilename( const char *pszFilename ) |
|
{ |
|
if ( !pszFilename || !pszFilename[0] ) |
|
return false; |
|
|
|
if ( V_stristr( pszFilename, " " ) || V_stristr( pszFilename, "\t" ) ) // don't multiple spaces or tab |
|
return false; |
|
|
|
const char *extension = V_strrchr( pszFilename, '.' ); |
|
if ( extension ) |
|
{ |
|
if ( Q_stricmp( extension, ".log" ) && Q_stricmp( extension, ".txt" ) ) // must use .log or .txt if an extension is specified |
|
return false; |
|
|
|
if ( extension == pszFilename ) // bad filename (just an extension) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
unsigned int COM_GetIdealDestinationCompressionBufferSize_Snappy( unsigned int uncompressedSize ) |
|
{ |
|
// 4 for the ID, plus whatever Snappy says it would need. |
|
return 4 + snappy::MaxCompressedLength( uncompressedSize ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void *COM_CompressBuffer_Snappy( const void *source, unsigned int sourceLen, unsigned int *compressedLen, unsigned int maxCompressedLen ) |
|
{ |
|
Assert( source ); |
|
Assert( compressedLen ); |
|
|
|
// Allocate a buffer big enough to hold the worst case. |
|
unsigned nMaxCompressedSize = COM_GetIdealDestinationCompressionBufferSize_Snappy( sourceLen ); |
|
char *pCompressed = (char*)malloc( nMaxCompressedSize ); |
|
if ( pCompressed == NULL ) |
|
return NULL; |
|
|
|
// Do the compression |
|
*(uint32 *)pCompressed = SNAPPY_ID; |
|
size_t compressed_length; |
|
snappy::RawCompress( (const char *)source, sourceLen, pCompressed + sizeof(uint32), &compressed_length ); |
|
compressed_length += 4; |
|
Assert( compressed_length <= nMaxCompressedSize ); |
|
|
|
// Check if this result is OK |
|
if ( maxCompressedLen != 0 && compressed_length > maxCompressedLen ) |
|
{ |
|
free( pCompressed ); |
|
return NULL; |
|
} |
|
|
|
*compressedLen = compressed_length; |
|
return pCompressed; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool COM_BufferToBufferCompress_Snappy( void *dest, unsigned int *destLen, const void *source, unsigned int sourceLen ) |
|
{ |
|
Assert( dest ); |
|
Assert( destLen ); |
|
Assert( source ); |
|
|
|
// Check if we need to use a temporary buffer |
|
unsigned nMaxCompressedSize = COM_GetIdealDestinationCompressionBufferSize_Snappy( sourceLen ); |
|
unsigned compressedLen = *destLen; |
|
if ( compressedLen < nMaxCompressedSize ) |
|
{ |
|
|
|
// Yep. Use the other function to allocate the buffer of the right size and comrpess into it |
|
void *temp = COM_CompressBuffer_Snappy( source, sourceLen, &compressedLen, compressedLen ); |
|
if ( temp == NULL ) |
|
return false; |
|
|
|
// Copy over the data |
|
V_memcpy( dest, temp, compressedLen ); |
|
*destLen = compressedLen; |
|
free( temp ); |
|
return true; |
|
} |
|
|
|
// We have room and should be able to compress directly |
|
*(uint32 *)dest = SNAPPY_ID; |
|
size_t compressed_length; |
|
snappy::RawCompress( (const char *)source, sourceLen, (char *)dest + sizeof(uint32), &compressed_length ); |
|
compressed_length += 4; |
|
Assert( compressed_length <= nMaxCompressedSize ); |
|
*destLen = compressed_length; |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
unsigned COM_GetIdealDestinationCompressionBufferSize_LZSS( unsigned int uncompressedSize ) |
|
{ |
|
// Our LZSS compressor doesn't need any extra space because it will stop and fail |
|
// as soon as it figures out it's unable to reduce the size of the data by more than |
|
// 32 bytes |
|
return uncompressedSize; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void *COM_CompressBuffer_LZSS( const void *source, unsigned int sourceLen, unsigned int *compressedLen, unsigned int maxCompressedLen ) |
|
{ |
|
Assert( source ); |
|
Assert( compressedLen ); |
|
|
|
CLZSS s; |
|
unsigned int uCompressedLen = 0; |
|
byte *pbOut = s.Compress( (const byte *)source, sourceLen, &uCompressedLen ); |
|
if ( pbOut && uCompressedLen > 0 && ( uCompressedLen <= maxCompressedLen || maxCompressedLen == 0 ) ) |
|
{ |
|
*compressedLen = uCompressedLen; |
|
return pbOut; |
|
} |
|
|
|
if ( pbOut ) |
|
{ |
|
free( pbOut ); |
|
} |
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
bool COM_BufferToBufferCompress_LZSS( void *dest, unsigned int *destLen, const void *source, unsigned int sourceLen ) |
|
{ |
|
Assert( dest ); |
|
Assert( destLen ); |
|
Assert( source ); |
|
|
|
CLZSS s; |
|
unsigned int uCompressedLen = 0; |
|
if ( !s.CompressNoAlloc( (const byte *)source, sourceLen, (unsigned char *)dest, &uCompressedLen ) ) |
|
return false; |
|
|
|
*destLen = uCompressedLen; |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
int COM_GetUncompressedSize( const void *compressed, unsigned int compressedLen ) |
|
{ |
|
const lzss_header_t *pHeader = (const lzss_header_t *)compressed; |
|
|
|
// Check for our own LZSS compressed data |
|
if ( ( compressedLen >= sizeof(lzss_header_t) ) && pHeader->id == LZSS_ID ) |
|
return LittleLong( pHeader->actualSize ); |
|
|
|
// Check for Snappy compressed |
|
if ( compressedLen > sizeof(pHeader->id) && pHeader->id == SNAPPY_ID ) |
|
{ |
|
size_t snappySize; |
|
if ( snappy::GetUncompressedLength( (const char *)compressed + sizeof(pHeader->id), compressedLen-sizeof(pHeader->id), &snappySize ) ) |
|
return (int)snappySize; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Generic buffer decompression from source into dest |
|
//----------------------------------------------------------------------------- |
|
bool COM_BufferToBufferDecompress( void *dest, unsigned int *destLen, const void *source, unsigned int sourceLen ) |
|
{ |
|
int nDecompressedSize = COM_GetUncompressedSize( source, sourceLen ); |
|
if ( nDecompressedSize >= 0 ) |
|
{ |
|
|
|
// Check buffer size |
|
if ( (unsigned)nDecompressedSize > *destLen ) |
|
{ |
|
Warning( "NET_BufferToBufferDecompress with improperly sized dest buffer (%u in, %u needed)\n", *destLen, nDecompressedSize ); |
|
return false; |
|
} |
|
|
|
const lzss_header_t *pHeader = (const lzss_header_t *)source; |
|
if ( pHeader->id == LZSS_ID ) |
|
{ |
|
CLZSS s; |
|
int nActualDecompressedSize = s.SafeUncompress( (byte *)source, (byte *)dest, *destLen ); |
|
if ( nActualDecompressedSize != nDecompressedSize ) |
|
{ |
|
Warning( "NET_BufferToBufferDecompress: header said %d bytes would be decompressed, but we LZSS decompressed %d\n", nDecompressedSize, nActualDecompressedSize ); |
|
return false; |
|
} |
|
*destLen = nDecompressedSize; |
|
return true; |
|
} |
|
|
|
if ( pHeader->id == SNAPPY_ID ) |
|
{ |
|
if ( !snappy::RawUncompress( (const char *)source + 4, sourceLen - 4, (char *)dest ) ) |
|
{ |
|
Warning( "NET_BufferToBufferDecompress: Snappy decompression failed\n" ); |
|
return false; |
|
} |
|
*destLen = nDecompressedSize; |
|
return true; |
|
} |
|
|
|
// Mismatch between this routine and COM_GetUncompressedSize |
|
AssertMsg( false, "Unknown compression type?" ); |
|
return false; |
|
} |
|
else |
|
{ |
|
if ( sourceLen > *destLen ) |
|
{ |
|
Warning( "NET_BufferToBufferDecompress with improperly sized dest buffer (%u in, %u needed)\n", *destLen, sourceLen ); |
|
return false; |
|
} |
|
|
|
V_memcpy( dest, source, sourceLen ); |
|
*destLen = sourceLen; |
|
} |
|
|
|
return true; |
|
}
|
|
|