From 425decdf7e9284d15aa726e3ae96b9942fb0e3ea Mon Sep 17 00:00:00 2001 From: IronClawTrem Date: Sun, 16 Feb 2020 03:40:06 +0000 Subject: create tremded branch --- src/qcommon/common.cpp | 3662 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3662 insertions(+) create mode 100644 src/qcommon/common.cpp (limited to 'src/qcommon/common.cpp') diff --git a/src/qcommon/common.cpp b/src/qcommon/common.cpp new file mode 100644 index 0000000..051f475 --- /dev/null +++ b/src/qcommon/common.cpp @@ -0,0 +1,3662 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2013 Darklegion Development +Copyright (C) 2015-2019 GrangerHub + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 3 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, see + +=========================================================================== +*/ + +// common.c -- misc functions used in client and server + +#include "qcommon.h" + +#include +#ifdef _WIN32 +#include +#else +#include +//#include // umask +#endif + +#include "sys/sys_shared.h" + +#include "cmd.h" +#include "crypto.h" +#include "cvar.h" +#include "files.h" +#define JSON_IMPLEMENTATION +#include "json.h" +#include "msg.h" +#include "q_shared.h" +#include "vm.h" + +int demo_protocols[] = { PROTOCOL_VERSION, 70, 69, 0 }; + +#define MAX_NUM_ARGVS 50 + +#define MIN_DEDICATED_COMHUNKMEGS 16 +#define MIN_COMHUNKMEGS 256 +#define DEF_COMHUNKMEGS 256 +#define DEF_COMZONEMEGS 48 +#define DEF_COMHUNKMEGS_S XSTRING(DEF_COMHUNKMEGS) +#define DEF_COMZONEMEGS_S XSTRING(DEF_COMZONEMEGS) + +int com_argc; +char* com_argv[MAX_NUM_ARGVS+1]; + +jmp_buf abortframe; // an ERR_DROP occured, exit the entire frame + + +FILE *debuglogfile; +static fileHandle_t pipefile; +static fileHandle_t logfile; +fileHandle_t com_journalFile; // events are written here +fileHandle_t com_journalDataFile; // config files are written here + +cvar_t *com_speeds; +cvar_t *com_developer; +cvar_t *com_dedicated; +cvar_t *com_timescale; +cvar_t *com_fixedtime; +cvar_t *com_journal; +cvar_t *com_maxfps; +cvar_t *com_altivec; +cvar_t *com_timedemo; +cvar_t *com_sv_running; +cvar_t *com_cl_running; +cvar_t *com_logfile; // 1 = buffer log, 2 = flush after each print +cvar_t *com_pipefile; +cvar_t *com_showtrace; +cvar_t *com_version; +cvar_t *com_buildScript; // for automated data building scripts +#ifdef CINEMATICS_INTRO +cvar_t *com_introPlayed; +#endif +cvar_t *cl_paused; +cvar_t *sv_paused; +cvar_t *cl_packetdelay; +cvar_t *sv_packetdelay; +cvar_t *com_cameraMode; +cvar_t *com_ansiColor; +cvar_t *com_unfocused; +cvar_t *com_maxfpsUnfocused; +cvar_t *com_minimized; +cvar_t *com_maxfpsMinimized; +cvar_t *com_standalone; +cvar_t *com_gamename; +cvar_t *com_protocol; +#ifdef LEGACY_PROTOCOL +cvar_t *com_legacyprotocol; +#endif +cvar_t *com_basegame; +cvar_t *com_homepath; +cvar_t *com_busyWait; + +#if id386 +void (QDECL *Q_SnapVector)(vec3_t vec); +#endif + +// com_speeds times +int time_game; +int time_frontend; // renderer frontend time +int time_backend; // renderer backend time + +int com_frameTime; +int com_frameNumber; + +bool com_errorEntered = false; +bool com_fullyInitialized = false; +bool com_gameRestarting = false; + +char com_errorMessage[MAXPRINTMSG]; + +void Com_WriteConfig_f( void ); +void CIN_CloseAllVideos( void ); + +//============================================================================ + +static char* rd_buffer; +static unsigned int rd_buffersize; +static void (*rd_flush)( char *buffer ); + +void Com_BeginRedirect (char *buffer, int buffersize, void (*flush)( char *) ) +{ + if (!buffer || !buffersize || !flush) + return; + rd_buffer = buffer; + rd_buffersize = buffersize; + rd_flush = flush; + + *rd_buffer = 0; +} + +void Com_EndRedirect(void) +{ + if ( rd_flush ) + rd_flush(rd_buffer); + + rd_buffer = NULL; + rd_buffersize = 0; + rd_flush = NULL; +} + +/* +============= +Com_Printf + +Both client and server can use this, and it will output +to the apropriate place. + +A raw string should NEVER be passed as fmt, because of "%f" type crashers. +============= +*/ +void QDECL Com_Printf( const char *fmt, ... ) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + static bool opening_qconsole = false; + + + va_start (argptr,fmt); + Q_vsnprintf (msg, sizeof(msg), fmt, argptr); + va_end (argptr); + + if ( rd_buffer ) + { + if ((strlen(msg) + strlen(rd_buffer)) > (rd_buffersize - 1)) + { + rd_flush(rd_buffer); + *rd_buffer = 0; + } + Q_strcat(rd_buffer, rd_buffersize, msg); + // TTimo nooo .. that would defeat the purpose + //rd_flush(rd_buffer); + //*rd_buffer = 0; + return; + } + +#ifndef DEDICATED + CL_ConsolePrint( msg ); +#endif + + Q_StripIndentMarker( msg ); + + // echo to dedicated console and early console + Sys_Print( msg ); + + // logfile + if ( com_logfile && com_logfile->integer ) { + // TTimo: only open the qconsole.log if the filesystem is in an initialized state + // also, avoid recursing in the qconsole.log opening (i.e. if fs_debug is on) + if ( !logfile && FS_Initialized() && !opening_qconsole) { + struct tm *newtime; + time_t aclock; + + opening_qconsole = true; + + time( &aclock ); + newtime = localtime( &aclock ); + + logfile = FS_FOpenFileWrite( "qconsole.log" ); + + if(logfile) + { + Com_Printf( "logfile opened on %s\n", asctime( newtime ) ); + + if ( com_logfile->integer > 1 ) + { + // force it to not buffer so we get valid + // data even if we are crashing + FS_ForceFlush(logfile); + } + } + else + { + Com_Printf("Opening qconsole.log failed!\n"); + Cvar_SetValue("logfile", 0); + } + + opening_qconsole = false; + } + if ( logfile && FS_Initialized()) { + FS_Write(msg, strlen(msg), logfile); + } + } +} + + +/* +================ +Com_DPrintf + +A Com_Printf that only shows up if the "developer" cvar is set +================ +*/ +void QDECL Com_DPrintf( const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + if ( !com_developer || !com_developer->integer ) + return; + + va_start(argptr,fmt); + Q_vsnprintf(msg, sizeof(msg), fmt, argptr); + va_end(argptr); + + Com_Printf("%s", msg); +} + +/* +============= +Com_Error + +Both client and server can use this, and it will +do the appropriate thing. +============= +*/ +void QDECL Com_Error( int code, const char *fmt, ... ) +{ + va_list argptr; + static int lastErrorTime; + static int errorCount; + int currentTime; + + if(com_errorEntered) + Sys_Error("recursive error after: %s", com_errorMessage); + + com_errorEntered = true; + + Cvar_Set("com_errorCode", va("%i", code)); + + // when we are running automated scripts, make sure we + // know if anything failed + if ( com_buildScript && com_buildScript->integer ) + code = ERR_FATAL; + + // if we are getting a solid stream of ERR_DROP, do an ERR_FATAL + currentTime = Sys_Milliseconds(); + if ( currentTime - lastErrorTime < 100 ) + { + if ( ++errorCount > 3 ) + code = ERR_FATAL; + } + else + { + errorCount = 0; + } + lastErrorTime = currentTime; + + va_start(argptr,fmt); + Q_vsnprintf(com_errorMessage, sizeof(com_errorMessage),fmt,argptr); + va_end(argptr); + + if ( code != ERR_DISCONNECT ) + Cvar_Set("com_errorMessage", com_errorMessage); + + if (code == ERR_DISCONNECT || code == ERR_SERVERDISCONNECT) + { + VM_Forced_Unload_Start(); + SV_Shutdown( "Server disconnected" ); + CL_Disconnect( true ); + CL_FlushMemory( ); + VM_Forced_Unload_Done(); + // make sure we can get at our local stuff + FS_PureServerSetLoadedPaks("", ""); + com_errorEntered = false; + longjmp (abortframe, -1); + } + else if (code == ERR_DROP || code == ERR_RECONNECT) + { + Com_Printf ("********************\nERROR: %s\n********************\n", com_errorMessage); + VM_Forced_Unload_Start(); + SV_Shutdown(va("Server crashed: %s", com_errorMessage)); + CL_Disconnect( true ); + CL_FlushMemory( ); + VM_Forced_Unload_Done(); + FS_PureServerSetLoadedPaks("", ""); + com_errorEntered = false; + + static int reconnectCount = 0; + if ( code == ERR_RECONNECT && reconnectCount <= 0 ) + { + reconnectCount++; + Cbuf_AddText("reconnect\n"); + } + else + { + reconnectCount = 0; + } + + longjmp(abortframe, -1); + } + else + { + VM_Forced_Unload_Start(); + CL_Shutdown(va("Client fatal crashed: %s", com_errorMessage), true, true); + SV_Shutdown(va("Server fatal crashed: %s", com_errorMessage)); + VM_Forced_Unload_Done(); + } + + Com_Shutdown(); + + Sys_Error("%s", com_errorMessage); +} + +/* +============= +Com_Quit_f + +Both client and server can use this, and it will +do the apropriate things. +============= +*/ +void Engine_Exit(const char* p ) +{ + // don't try to shutdown if we are in a recursive error + if ( !com_errorEntered ) + { + // Some VMs might execute "quit" command directly, + // which would trigger an unload of active VM error. + // Sys_Quit will kill this process anyways, so + // a corrupt call stack makes no difference + VM_Forced_Unload_Start(); + SV_Shutdown(p[0] ? p : "Server quit"); + CL_Shutdown(p[0] ? p : "Client quit", true, true); + VM_Forced_Unload_Done(); + Com_Shutdown(); + FS_Shutdown(true); + } + Sys_Quit (); +} + +void Com_Quit_f( void ) +{ + char *p = Cmd_Args(); + Engine_Exit(p); +} + + + +/* +============================================================================ + +COMMAND LINE FUNCTIONS + ++ characters seperate the commandLine string into multiple console +command lines. + +All of these are valid: + +tremulous +set test blah +map test +tremulous set test blah+map test +tremulous set test blah + map test + +============================================================================ +*/ + +#define MAX_CONSOLE_LINES 32 +int com_numConsoleLines; +char* com_consoleLines[MAX_CONSOLE_LINES]; + +/* +================== +Com_ParseCommandLine + +Break it up into multiple console lines +================== +*/ +void Com_ParseCommandLine( char *commandLine ) +{ + int inq = 0; + com_consoleLines[0] = commandLine; + com_numConsoleLines = 1; + + while ( *commandLine ) + { + if ( *commandLine == '"' ) + inq = !inq; + + // look for a + seperating character + // if commandLine came from a file, we might have real line seperators + if ( (commandLine[0] == '+' && !inq) || commandLine[0] == '\n' || commandLine[0] == '\r' ) + { + if ( com_numConsoleLines == MAX_CONSOLE_LINES ) + return; + + com_consoleLines[com_numConsoleLines] = commandLine + 1; + com_numConsoleLines++; + *commandLine = 0; + } + commandLine++; + } +} + +/* +=================== +Com_SafeMode + +Check for "safe" on the command line, which will +skip loading of autogen.cfg +=================== +*/ +bool Com_SafeMode( void ) +{ + for ( int i = 0 ; i < com_numConsoleLines ; i++ ) + { + Cmd_TokenizeString(com_consoleLines[i]); + if ( !Q_stricmp(Cmd_Argv(0), "safe") + || !Q_stricmp(Cmd_Argv(0), "cvar_restart") ) + { + com_consoleLines[i][0] = 0; + return true; + } + } + + return false; +} + +/* +=============== +Com_StartupVariable + +Searches for command line parameters that are set commands. +If match is not NULL, only that cvar will be looked for. +That is necessary because cddir and basedir need to be set +before the filesystem is started, but all other sets should +be after execing the config and default. +=============== +*/ +void Com_StartupVariable( const char *match ) +{ + for (int i = 0 ; i < com_numConsoleLines ; i++) + { + Cmd_TokenizeString( com_consoleLines[i] ); + if ( strcmp( Cmd_Argv(0), "set" ) ) { + continue; + } + + const char* s = Cmd_Argv(1); + if(!match || !strcmp(s, match)) + { + if(Cvar_Flags(s) == CVAR_NONEXISTENT) + Cvar_Get(s, Cmd_ArgsFrom(2), CVAR_USER_CREATED); + else + Cvar_Set2(s, Cmd_ArgsFrom(2), false); + } + } +} + +/* +================= +Com_AddStartupCommands + +Adds command line parameters as script statements +Commands are seperated by + signs + +Returns true if any late commands were added +================= +*/ +bool Com_AddStartupCommands( void ) +{ + bool added = false; + + // quote every token, so args with semicolons can work + for ( int i = 0 ; i < com_numConsoleLines ; i++) + { + if ( !com_consoleLines[i] || !com_consoleLines[i][0] ) + continue; + + // set commands already added with Com_StartupVariable + if ( !Q_stricmpn(com_consoleLines[i], "set ", 4) ) + continue; + + added = true; + Cbuf_AddText( com_consoleLines[i] ); + Cbuf_AddText( "\n" ); + } + + return added; +} + +//============================================================================ + +void Info_Print( const char *s ) +{ + char key[BIG_INFO_KEY]; + char value[BIG_INFO_VALUE]; + char* o; + int l; + + if (*s == '\\') + s++; + + while (*s) + { + o = key; + while (*s && *s != '\\') + *o++ = *s++; + + l = o - key; + if (l < 20) + { + ::memset(o, ' ', 20-l); + key[20] = 0; + } + else + *o = 0; + Com_Printf("%s ", key); + + if (!*s) + { + Com_Printf("MISSING VALUE\n"); + return; + } + + o = value; + s++; + while (*s && *s != '\\') + *o++ = *s++; + *o = 0; + + if (*s) + s++; + + Com_Printf("%s\n", value); + } +} + +/* +============ +Com_StringContains +============ +*/ +char *Com_StringContains(char *str1, char *str2, int casesensitive) +{ + int len, i, j; + + len = strlen(str1) - strlen(str2); + for (i = 0; i <= len; i++, str1++) { + for (j = 0; str2[j]; j++) { + if (casesensitive) { + if (str1[j] != str2[j]) { + break; + } + } + else { + if (toupper(str1[j]) != toupper(str2[j])) { + break; + } + } + } + if (!str2[j]) { + return str1; + } + } + return NULL; +} + +/* +============ +Com_Filter +============ +*/ +int Com_Filter(const char* filter, char *name, int casesensitive) +{ + char buf[MAX_TOKEN_CHARS]; + char *ptr; + int i, found; + + while(*filter) { + if (*filter == '*') { + filter++; + for (i = 0; *filter; i++) { + if (*filter == '*' || *filter == '?') break; + buf[i] = *filter; + filter++; + } + buf[i] = '\0'; + if (strlen(buf)) { + ptr = Com_StringContains(name, buf, casesensitive); + if (!ptr) return false; + name = ptr + strlen(buf); + } + } + else if (*filter == '?') { + filter++; + name++; + } + else if (*filter == '[' && *(filter+1) == '[') { + filter++; + } + else if (*filter == '[') { + filter++; + found = false; + while(*filter && !found) { + if (*filter == ']' && *(filter+1) != ']') break; + if (*(filter+1) == '-' && *(filter+2) && (*(filter+2) != ']' || *(filter+3) == ']')) { + if (casesensitive) { + if (*name >= *filter && *name <= *(filter+2)) found = true; + } + else { + if (toupper(*name) >= toupper(*filter) && + toupper(*name) <= toupper(*(filter+2))) found = true; + } + filter += 3; + } + else { + if (casesensitive) { + if (*filter == *name) found = true; + } + else { + if (toupper(*filter) == toupper(*name)) found = true; + } + filter++; + } + } + if (!found) return false; + while(*filter) { + if (*filter == ']' && *(filter+1) != ']') break; + filter++; + } + filter++; + name++; + } + else { + if (casesensitive) { + if (*filter != *name) return false; + } + else { + if (toupper(*filter) != toupper(*name)) return false; + } + filter++; + name++; + } + } + return true; +} + +/* +============ +Com_FilterPath +============ +*/ +int Com_FilterPath(const char *filter, char *name, int casesensitive) +{ + int i; + char new_filter[MAX_QPATH]; + char new_name[MAX_QPATH]; + + for (i = 0; i < MAX_QPATH-1 && filter[i]; i++) { + if ( filter[i] == '\\' || filter[i] == ':' ) { + new_filter[i] = '/'; + } + else { + new_filter[i] = filter[i]; + } + } + new_filter[i] = '\0'; + for (i = 0; i < MAX_QPATH-1 && name[i]; i++) { + if ( name[i] == '\\' || name[i] == ':' ) { + new_name[i] = '/'; + } + else { + new_name[i] = name[i]; + } + } + new_name[i] = '\0'; + return Com_Filter(new_filter, new_name, casesensitive); +} + +/* +================ +Com_RealTime +================ +*/ +int Com_RealTime(qtime_t *qtime) +{ + time_t t; + struct tm *tms; + + t = time(NULL); + if (!qtime) + return t; + tms = localtime(&t); + if (tms) { + qtime->tm_sec = tms->tm_sec; + qtime->tm_min = tms->tm_min; + qtime->tm_hour = tms->tm_hour; + qtime->tm_mday = tms->tm_mday; + qtime->tm_mon = tms->tm_mon; + qtime->tm_year = tms->tm_year; + qtime->tm_wday = tms->tm_wday; + qtime->tm_yday = tms->tm_yday; + qtime->tm_isdst = tms->tm_isdst; + } + return t; +} + +/* +============================================================================== + +ZONE MEMORY ALLOCATION + +There is never any space between memblocks, and there will never be two +contiguous free memblocks. + +The rover can be left pointing at a non-empty block + +The zone calls are pretty much only used for small strings and structures, +all big things are allocated on the hunk. +============================================================================== +*/ + +#define ZONEID 0x1d4a11 +#define MINFRAGMENT 64 + +typedef struct zonedebug_s { + const char *label; + const char *file; + int line; + int allocSize; +} zonedebug_t; + +typedef struct memblock_s { + int size; // including the header and possibly tiny fragments + int tag; // a tag of 0 is a free block + struct memblock_s *next, *prev; + int id; // should be ZONEID +#ifdef ZONE_DEBUG + zonedebug_t d; +#endif +} memblock_t; + +typedef struct { + int size; // total bytes malloced, including header + int used; // total bytes used + memblock_t blocklist; // start / end cap for linked list + memblock_t *rover; +} memzone_t; + +// main zone for all "dynamic" memory allocation +memzone_t *mainzone; +// we also have a small zone for small allocations that would only +// fragment the main zone (think of cvar and cmd strings) +memzone_t *smallzone; + +void Z_CheckHeap( void ); + +/* +======================== +Z_ClearZone +======================== +*/ +void Z_ClearZone( memzone_t *zone, int size ) +{ + memblock_t *block; + + // set the entire zone to one free block + + zone->blocklist.next = zone->blocklist.prev = block = + (memblock_t *)( (byte *)zone + sizeof(memzone_t) ); + zone->blocklist.tag = 1; // in use block + zone->blocklist.id = 0; + zone->blocklist.size = 0; + zone->rover = block; + zone->size = size; + zone->used = 0; + + block->prev = block->next = &zone->blocklist; + block->tag = 0; // free block + block->id = ZONEID; + block->size = size - sizeof(memzone_t); +} + +/* +======================== +Z_AvailableZoneMemory +======================== +*/ +int Z_AvailableZoneMemory( memzone_t *zone ) +{ + return zone->size - zone->used; +} + +/* +======================== +Z_AvailableMemory +======================== +*/ +int Z_AvailableMemory( void ) +{ + return Z_AvailableZoneMemory( mainzone ); +} + +/* +======================== +Z_Free +======================== +*/ +void Z_Free( void *ptr ) +{ + memblock_t *block, *other; + memzone_t *zone; + + if (!ptr) { + Com_Printf(S_COLOR_YELLOW "Z_Free: NULL pointer" ); + return; + } + + block = (memblock_t *) ( (byte *)ptr - sizeof(memblock_t)); + if (block->id != ZONEID) { + Com_Error( ERR_FATAL, "Z_Free: freed a pointer without ZONEID" ); + } + if (block->tag == 0) { + Com_Error( ERR_FATAL, "Z_Free: freed a freed pointer" ); + } + // if static memory + if (block->tag == TAG_STATIC) { + return; + } + + // check the memory trash tester + if ( *(int *)((byte *)block + block->size - 4 ) != ZONEID ) { + Com_Error( ERR_FATAL, "Z_Free: memory block wrote past end" ); + } + + if (block->tag == TAG_SMALL) { + zone = smallzone; + } + else { + zone = mainzone; + } + + zone->used -= block->size; + // set the block to something that should cause problems + // if it is referenced... + ::memset( ptr, 0xaa, block->size - sizeof( *block ) ); + + block->tag = 0; // mark as free + + other = block->prev; + if (!other->tag) { + // merge with previous free block + other->size += block->size; + other->next = block->next; + other->next->prev = other; + if (block == zone->rover) { + zone->rover = other; + } + block = other; + } + + zone->rover = block; + + other = block->next; + if ( !other->tag ) { + // merge the next free block onto the end + block->size += other->size; + block->next = other->next; + block->next->prev = block; + } +} + + +/* +================ +Z_FreeTags +================ +*/ +void Z_FreeTags( int tag ) +{ + memzone_t *zone; + + if ( tag == TAG_SMALL ) + { + zone = smallzone; + } + else + { + zone = mainzone; + } + // use the rover as our pointer, because + // Z_Free automatically adjusts it + zone->rover = zone->blocklist.next; + do { + if ( zone->rover->tag == tag ) { + Z_Free( (void *)(zone->rover + 1) ); + continue; + } + zone->rover = zone->rover->next; + } while ( zone->rover != &zone->blocklist ); +} + + +/* +================ +Z_TagMalloc +================ +*/ +#ifdef ZONE_DEBUG +void *Z_TagMallocDebug( int size, int tag, const char *label, const char *file, int line ) +#else +void *Z_TagMalloc( int size, int tag ) +#endif +{ + int extra; + memblock_t *start, *rover, *_new, *base; + memzone_t *zone; + + if (!tag) + Com_Error( ERR_FATAL, "Z_TagMalloc: tried to use a 0 tag" ); + + if ( tag == TAG_SMALL ) + zone = smallzone; + else + zone = mainzone; + +#ifdef ZONE_DEBUG + int allocSize = size; +#endif + // + // scan through the block list looking for the first free block + // of sufficient size + // + size += sizeof(memblock_t); // account for size of block header + size += 4; // space for memory trash tester + size = PAD(size, sizeof(intptr_t)); // align to 32/64 bit boundary + + base = rover = zone->rover; + start = base->prev; + + do { + if (rover == start) + { + // scaned all the way around the list +#ifdef ZONE_DEBUG + Z_LogHeap(); + + Com_Error(ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone: %s, line: %d (%s)", + size, zone == smallzone ? "small" : "main", file, line, label); +#else + Com_Error(ERR_FATAL, "Z_Malloc: failed on allocation of %i bytes from the %s zone", + size, zone == smallzone ? "small" : "main"); +#endif + return NULL; + } + if (rover->tag) { + base = rover = rover->next; + } else { + rover = rover->next; + } + } while (base->tag || base->size < size); + + // + // found a block big enough + // + extra = base->size - size; + if (extra > MINFRAGMENT) { + // there will be a free fragment after the allocated block + _new = (memblock_t *) ((byte *)base + size ); + _new->size = extra; + _new->tag = 0; // free block + _new->prev = base; + _new->id = ZONEID; + _new->next = base->next; + _new->next->prev = _new; + base->next = _new; + base->size = size; + } + + base->tag = tag; // n_o longer a free block + + zone->rover = base->next; // next allocation will start looking here + zone->used += base->size; + + base->id = ZONEID; + +#ifdef ZONE_DEBUG + base->d.label = label; + base->d.file = file; + base->d.line = line; + base->d.allocSize = allocSize; +#endif + + // marker for memory trash testing + *(int *)((byte *)base + base->size - 4) = ZONEID; + + return (void *) ((byte *)base + sizeof(memblock_t)); +} + +/* +======================== +Z_Malloc +======================== +*/ +#ifdef ZONE_DEBUG +void *Z_MallocDebug( int size, const char *label, const char *file, int line ) +#else +void *Z_Malloc( int size ) +#endif +{ + void *buf; + + //Z_CheckHeap(); // XXX DEBUG + +#ifdef ZONE_DEBUG + buf = Z_TagMallocDebug( size, TAG_GENERAL, label, file, line ); +#else + buf = Z_TagMalloc( size, TAG_GENERAL ); +#endif + ::memset( buf, 0, size ); + + return buf; +} + +#ifdef ZONE_DEBUG +void *S_MallocDebug( int size, const char *label, const char *file, int line ) +{ + return Z_TagMallocDebug( size, TAG_SMALL, label, file, line ); +} +#else +void *S_Malloc( int size ) +{ + return Z_TagMalloc( size, TAG_SMALL ); +} +#endif + +/* +======================== +Z_CheckHeap +======================== +*/ +void Z_CheckHeap( void ) +{ + memblock_t *block; + + for (block = mainzone->blocklist.next ; ; block = block->next) + { + if (block->next == &mainzone->blocklist) + break; // all blocks have been hit + + if ( (byte *)block + block->size != (byte *)block->next) + Com_Error( ERR_FATAL, "Z_CheckHeap: block size does not touch the next block" ); + + if ( block->next->prev != block) + Com_Error( ERR_FATAL, "Z_CheckHeap: next block doesn't have proper back link" ); + + if ( !block->tag && !block->next->tag ) + Com_Error( ERR_FATAL, "Z_CheckHeap: two consecutive free blocks" ); + } +} + +/* +======================== +Z_LogZoneHeap +======================== +*/ +void Z_LogZoneHeap( memzone_t *zone, const char *name ) +{ +#ifdef ZONE_DEBUG + char dump[32], *ptr; + int i, j; +#endif + memblock_t *block; + char buf[4096]; + int size, allocSize, numBlocks; + + if (!logfile || !FS_Initialized()) + return; + + size = numBlocks = 0; +#ifdef ZONE_DEBUG + allocSize = 0; +#endif + Com_sprintf(buf, sizeof(buf), "\r\n================\r\n%s log\r\n================\r\n", name); + FS_Write(buf, strlen(buf), logfile); + + for (block = zone->blocklist.next ; block->next != &zone->blocklist; block = block->next) + { + if (block->tag) + { +#ifdef ZONE_DEBUG + ptr = ((char *) block) + sizeof(memblock_t); + j = 0; + for (i = 0; i < 20 && i < block->d.allocSize; i++) + { + if (ptr[i] >= 32 && ptr[i] < 127) { + dump[j++] = ptr[i]; + } + else { + dump[j++] = '_'; + } + } + dump[j] = '\0'; + Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s) [%s]\r\n", block->d.allocSize, block->d.file, block->d.line, block->d.label, dump); + FS_Write(buf, strlen(buf), logfile); + allocSize += block->d.allocSize; +#endif + size += block->size; + numBlocks++; + } + } +#ifdef ZONE_DEBUG + // subtract debug memory + size -= numBlocks * sizeof(zonedebug_t); +#else + allocSize = numBlocks * sizeof(memblock_t); // + 32 bit alignment +#endif + Com_sprintf(buf, sizeof(buf), "%d %s memory in %d blocks\r\n", size, name, numBlocks); + FS_Write(buf, strlen(buf), logfile); + Com_sprintf(buf, sizeof(buf), "%d %s memory overhead\r\n", size - allocSize, name); + FS_Write(buf, strlen(buf), logfile); +} + +/* +======================== +Z_LogHeap +======================== +*/ +void Z_LogHeap( void ) +{ + Z_LogZoneHeap( mainzone, "MAIN" ); + Z_LogZoneHeap( smallzone, "SMALL" ); +} + +// static mem blocks to reduce a lot of small zone overhead +typedef struct memstatic_s { + memblock_t b; + byte mem[2]; +} memstatic_t; + +memstatic_t emptystring = { + {(sizeof(memblock_t)+2 + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'\0', '\0'} +}; + +memstatic_t numberstring[] = { + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'0', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'1', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'2', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'3', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'4', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} } +}; + +/* +======================== +CopyString + +NOTE: never write over the memory CopyString returns because +memory from a memstatic_t might be returned +======================== +*/ +char *CopyString( const char *in ) +{ + char *out; + + if (!in[0]) { + return ((char *)&emptystring) + sizeof(memblock_t); + } + else if (!in[1]) { + if (in[0] >= '0' && in[0] <= '9') { + return ((char *)&numberstring[in[0]-'0']) + sizeof(memblock_t); + } + } + out = (char*)S_Malloc(strlen(in)+1); + strcpy (out, in); + return out; +} + +/* +============================================================================== + +Goals: + reproducable without history effects -- no out of memory errors on weird map to map changes + allow restarting of the client without fragmentation + minimize total pages in use at run time + minimize total pages needed during load time + + Single block of memory with stack allocators coming from both ends towards the middle. + + One side is designated the temporary memory allocator. + + Temporary memory can be allocated and freed in any order. + + A highwater mark is kept of the most in use at any time. + + When there is no temporary memory allocated, the permanent and temp sides + can be switched, allowing the already touched temp memory to be used for + permanent storage. + + Temp memory must never be allocated on two ends at once, or fragmentation + could occur. + + If we have any in-use temp memory, additional temp allocations must come from + that side. + + If not, we can choose to make either side the new temp side and push future + permanent allocations to the other side. Permanent allocations should be + kept on the side that has the current greatest wasted highwater mark. + +============================================================================== +*/ + + +#define HUNK_MAGIC 0x89537892 +#define HUNK_FREE_MAGIC 0x89537893 + +typedef struct { + unsigned int magic; + unsigned int size; +} hunkHeader_t; + +typedef struct { + int mark; + int permanent; + int temp; + int tempHighwater; +} hunkUsed_t; + +typedef struct hunkblock_s { + int size; + byte printed; + struct hunkblock_s *next; + const char *label; + const char *file; + int line; +} hunkblock_t; + +static hunkblock_t *hunkblocks; + +static hunkUsed_t hunk_low, hunk_high; +static hunkUsed_t *hunk_permanent, *hunk_temp; + +static byte* s_hunkData = NULL; +static int s_hunkTotal; + +static int s_zoneTotal; +static int s_smallZoneTotal; + +/* +================= +Com_Meminfo_f +================= +*/ +void Com_Meminfo_f( void ) +{ + memblock_t *block; + int zoneBytes, zoneBlocks; + int smallZoneBytes; + int botlibBytes, rendererBytes; + int unused; + + zoneBytes = 0; + botlibBytes = 0; + rendererBytes = 0; + zoneBlocks = 0; + for (block = mainzone->blocklist.next ; ; block = block->next) { + if ( Cmd_Argc() != 1 ) { + Com_Printf ("block:%p size:%7i tag:%3i\n", + (void *)block, block->size, block->tag); + } + if ( block->tag ) { + zoneBytes += block->size; + zoneBlocks++; + if ( block->tag == TAG_BOTLIB ) { + botlibBytes += block->size; + } else if ( block->tag == TAG_RENDERER ) { + rendererBytes += block->size; + } + } + + if (block->next == &mainzone->blocklist) { + break; // all blocks have been hit + } + if ( (byte *)block + block->size != (byte *)block->next) { + Com_Printf ("ERROR: block size does not touch the next block\n"); + } + if ( block->next->prev != block) { + Com_Printf ("ERROR: next block doesn't have proper back link\n"); + } + if ( !block->tag && !block->next->tag ) { + Com_Printf ("ERROR: two consecutive free blocks\n"); + } + } + + smallZoneBytes = 0; + for (block = smallzone->blocklist.next ; ; block = block->next) + { + if ( block->tag ) + smallZoneBytes += block->size; + + if (block->next == &smallzone->blocklist) + break; // all blocks have been hit + } + + Com_Printf( "%8i bytes total hunk\n", s_hunkTotal ); + Com_Printf( "%8i bytes total zone\n", s_zoneTotal ); + Com_Printf( "\n" ); + Com_Printf( "%8i low mark\n", hunk_low.mark ); + Com_Printf( "%8i low permanent\n", hunk_low.permanent ); + if ( hunk_low.temp != hunk_low.permanent ) { + Com_Printf( "%8i low temp\n", hunk_low.temp ); + } + Com_Printf( "%8i low tempHighwater\n", hunk_low.tempHighwater ); + Com_Printf( "\n" ); + Com_Printf( "%8i high mark\n", hunk_high.mark ); + Com_Printf( "%8i high permanent\n", hunk_high.permanent ); + if ( hunk_high.temp != hunk_high.permanent ) { + Com_Printf( "%8i high temp\n", hunk_high.temp ); + } + Com_Printf( "%8i high tempHighwater\n", hunk_high.tempHighwater ); + Com_Printf( "\n" ); + Com_Printf( "%8i total hunk in use\n", hunk_low.permanent + hunk_high.permanent ); + unused = 0; + if ( hunk_low.tempHighwater > hunk_low.permanent ) { + unused += hunk_low.tempHighwater - hunk_low.permanent; + } + if ( hunk_high.tempHighwater > hunk_high.permanent ) { + unused += hunk_high.tempHighwater - hunk_high.permanent; + } + Com_Printf( "%8i unused highwater\n", unused ); + Com_Printf( "\n" ); + Com_Printf( "%8i bytes in %i zone blocks\n", zoneBytes, zoneBlocks ); + Com_Printf( " %8i bytes in dynamic botlib\n", botlibBytes ); + Com_Printf( " %8i bytes in dynamic renderer\n", rendererBytes ); + Com_Printf( " %8i bytes in dynamic other\n", zoneBytes - ( botlibBytes + rendererBytes ) ); + Com_Printf( " %8i bytes in small Zone memory\n", smallZoneBytes ); +} + +/* +=============== +Com_TouchMemory + +Touch all known used data to make sure it is paged in +=============== +*/ +void Com_TouchMemory( void ) +{ + int start, end; + int i, j; + int sum; + memblock_t *block; + + Z_CheckHeap(); + + start = Sys_Milliseconds(); + + sum = 0; + + j = hunk_low.permanent >> 2; + for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page + sum += ((int *)s_hunkData)[i]; + } + + i = ( s_hunkTotal - hunk_high.permanent ) >> 2; + j = hunk_high.permanent >> 2; + for ( ; i < j ; i+=64 ) { // only need to touch each page + sum += ((int *)s_hunkData)[i]; + } + + for (block = mainzone->blocklist.next ; ; block = block->next) { + if ( block->tag ) { + j = block->size >> 2; + for ( i = 0 ; i < j ; i+=64 ) { // only need to touch each page + sum += ((int *)block)[i]; + } + } + if ( block->next == &mainzone->blocklist ) { + break; // all blocks have been hit + } + } + + end = Sys_Milliseconds(); + + Com_Printf( "Com_TouchMemory: %i msec\n", end - start ); +} + + + +/* +================= +Com_InitZoneMemory +================= +*/ +void Com_InitSmallZoneMemory( void ) +{ + s_smallZoneTotal = (512 * 1024); + smallzone = (memzone_t*)calloc(s_smallZoneTotal, 1); + if ( !smallzone ) + Com_Error(ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / (1024*1024)); + + Z_ClearZone( smallzone, s_smallZoneTotal ); +} + +void Com_InitZoneMemory( void ) +{ + // Please note: com_zoneMegs can only be set on the command line, and not + // in q3config.cfg or Com_StartupVariable, as they haven't been executed by + // this point. It's a chicken and egg problem. We need the memory manager + // configured to handle those places where you would configure the memory + // manager. + + // allocate the random block zone + cvar_t* cv = Cvar_Get( "com_zoneMegs", DEF_COMZONEMEGS_S, CVAR_LATCH | CVAR_ARCHIVE ); + + if ( cv->integer < DEF_COMZONEMEGS ) { + s_zoneTotal = 1024 * 1024 * DEF_COMZONEMEGS; + } else { + s_zoneTotal = cv->integer * 1024 * 1024; + } + + mainzone = (memzone_t*)calloc( s_zoneTotal, 1 ); + if ( !mainzone ) { + Com_Error( ERR_FATAL, "Zone data failed to allocate %i megs", s_zoneTotal / (1024*1024) ); + } + Z_ClearZone( mainzone, s_zoneTotal ); +} + +/* +================= +Hunk_Log +================= +*/ +void Hunk_Log( void) +{ + char buf[4096]; + + if (!logfile || !FS_Initialized()) + return; + + int size = 0; + int numBlocks = 0; + + Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk log\r\n================\r\n"); + FS_Write(buf, strlen(buf), logfile); + + for ( hunkblock_t* block = hunkblocks; block; block = block->next ) + { +#ifdef HUNK_DEBUG + Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", block->size, block->file, block->line, block->label); + FS_Write(buf, strlen(buf), logfile); +#endif + size += block->size; + numBlocks++; + } + + Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size); + FS_Write(buf, strlen(buf), logfile); + + Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks); + FS_Write(buf, strlen(buf), logfile); +} + +/* +================= +Hunk_SmallLog +================= +*/ +void Hunk_SmallLog(void) +{ + char buf[4096]; + + if (!logfile || !FS_Initialized()) + return; + + for ( hunkblock_t* block = hunkblocks ; block; block = block->next ) + block->printed = false; + + int size = 0; + int numBlocks = 0; + + Com_sprintf(buf, sizeof(buf), "\r\n================\r\nHunk Small log\r\n================\r\n"); + FS_Write(buf, strlen(buf), logfile); + + for ( hunkblock_t* block = hunkblocks; block; block = block->next ) + { + if (block->printed) + continue; + + int locsize = block->size; + for ( hunkblock_t* block2 = block->next; block2; block2 = block2->next ) + { + if (block->line != block2->line) + continue; + + if (Q_stricmp(block->file, block2->file)) + continue; + + size += block2->size; + locsize += block2->size; + block2->printed = true; + } +#ifdef HUNK_DEBUG + Com_sprintf(buf, sizeof(buf), "size = %8d: %s, line: %d (%s)\r\n", locsize, block->file, block->line, block->label); + FS_Write(buf, strlen(buf), logfile); +#endif + size += block->size; + numBlocks++; + } + Com_sprintf(buf, sizeof(buf), "%d Hunk memory\r\n", size); + FS_Write(buf, strlen(buf), logfile); + + Com_sprintf(buf, sizeof(buf), "%d hunk blocks\r\n", numBlocks); + FS_Write(buf, strlen(buf), logfile); +} + +/* +================= +Com_InitHunkZoneMemory +================= +*/ +void Com_InitHunkMemory( void ) +{ + cvar_t *cv; + int nMinAlloc; + const char *pMsg = NULL; + + // make sure the file system has allocated and "not" freed any temp blocks + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if (FS_LoadStack() != 0) + Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero"); + + // allocate the stack based hunk allocator + cv = Cvar_Get( "com_hunkMegs", DEF_COMHUNKMEGS_S, CVAR_LATCH | CVAR_ARCHIVE ); + Cvar_SetDescription(cv, "The size of the hunk memory segment"); + + // if we are not dedicated min allocation is 56, otherwise min is 1 + if (com_dedicated && com_dedicated->integer) + { + nMinAlloc = MIN_DEDICATED_COMHUNKMEGS; + pMsg = "Minimum com_hunkMegs for a dedicated server is %i, allocating %i megs.\n"; + } + else + { + nMinAlloc = MIN_COMHUNKMEGS; + pMsg = "Minimum com_hunkMegs is %i, allocating %i megs.\n"; + } + + if ( cv->integer < nMinAlloc ) + { + s_hunkTotal = 1024 * 1024 * nMinAlloc; + Com_Printf(pMsg, nMinAlloc, s_hunkTotal / (1024 * 1024)); + } + else + { + s_hunkTotal = cv->integer * 1024 * 1024; + } + + s_hunkData = (byte*)calloc( s_hunkTotal + 31, 1 ); + if ( !s_hunkData ) + { + Com_Error( ERR_FATAL, "Hunk data failed to allocate %i megs", s_hunkTotal / (1024*1024) ); + } + // cacheline align + s_hunkData = (byte *) ( ( (intptr_t)s_hunkData + 31 ) & ~31 ); + Hunk_Clear(); + + Cmd_AddCommand( "meminfo", Com_Meminfo_f ); +#ifdef ZONE_DEBUG + Cmd_AddCommand( "zonelog", Z_LogHeap ); +#endif +#ifdef HUNK_DEBUG + Cmd_AddCommand( "hunklog", Hunk_Log ); + Cmd_AddCommand( "hunksmalllog", Hunk_SmallLog ); +#endif +} + +/* +==================== +Hunk_MemoryRemaining +==================== +*/ +int Hunk_MemoryRemaining( void ) +{ + int low = hunk_low.permanent > hunk_low.temp ? hunk_low.permanent : hunk_low.temp; + int high = hunk_high.permanent > hunk_high.temp ? hunk_high.permanent : hunk_high.temp; + return s_hunkTotal - ( low + high ); +} + +/* +=================== +Hunk_SetMark + +The server calls this after the level and game VM have been loaded +=================== +*/ +void Hunk_SetMark( void ) +{ + hunk_low.mark = hunk_low.permanent; + hunk_high.mark = hunk_high.permanent; +} + +/* +================= +Hunk_ClearToMark + +The client calls this before starting a vid_restart or snd_restart +================= +*/ +void Hunk_ClearToMark( void ) +{ + hunk_low.permanent = hunk_low.temp = hunk_low.mark; + hunk_high.permanent = hunk_high.temp = hunk_high.mark; +} + +/* +================= +Hunk_CheckMark +================= +*/ +bool Hunk_CheckMark( void ) +{ + if( hunk_low.mark || hunk_high.mark ) + return true; + return false; +} + +void CL_ShutdownCGame( void ); +void CL_ShutdownUI( void ); +void SV_ShutdownGameProgs( void ); + +/* +================= +Hunk_Clear + +The server calls this before shutting down or loading a new map +================= +*/ +void Hunk_Clear( void ) +{ +#ifndef DEDICATED + CL_ShutdownCGame(); + CL_ShutdownUI(); +#endif + SV_ShutdownGameProgs(); +#ifndef DEDICATED + CIN_CloseAllVideos(); +#endif + hunk_low.mark = 0; + hunk_low.permanent = 0; + hunk_low.temp = 0; + hunk_low.tempHighwater = 0; + + hunk_high.mark = 0; + hunk_high.permanent = 0; + hunk_high.temp = 0; + hunk_high.tempHighwater = 0; + + hunk_permanent = &hunk_low; + hunk_temp = &hunk_high; + + Com_Printf( "Hunk_Clear: reset the hunk ok\n" ); + VM_Clear(); +#ifdef HUNK_DEBUG + hunkblocks = NULL; +#endif +} + +static void Hunk_SwapBanks( void ) +{ + hunkUsed_t *swap; + + // can't swap banks if there is any temp already allocated + if ( hunk_temp->temp != hunk_temp->permanent ) + return; + + // if we have a larger highwater mark on this side, start making + // our permanent allocations here and use the other side for temp + if ( hunk_temp->tempHighwater - hunk_temp->permanent + > hunk_permanent->tempHighwater - hunk_permanent->permanent ) + { + swap = hunk_temp; + hunk_temp = hunk_permanent; + hunk_permanent = swap; + } +} + +/* +================= +Hunk_Alloc + +Allocate permanent (until the hunk is cleared) memory +================= +*/ +#ifdef HUNK_DEBUG +void *Hunk_AllocDebug( int size, ha_pref preference, const char *label, const char *file, int line ) +#else +void *Hunk_Alloc( int size, ha_pref preference ) +#endif +{ + void* buf; + + if ( s_hunkData == NULL) + { + Com_Error( ERR_FATAL, "Hunk_Alloc: Hunk memory system not initialized" ); + } + + // can't do preference if there is any temp allocated + if (preference == h_dontcare || hunk_temp->temp != hunk_temp->permanent) { + Hunk_SwapBanks(); + } else { + if (preference == h_low && hunk_permanent != &hunk_low) { + Hunk_SwapBanks(); + } else if (preference == h_high && hunk_permanent != &hunk_high) { + Hunk_SwapBanks(); + } + } + +#ifdef HUNK_DEBUG + size += sizeof(hunkblock_t); +#endif + + // round to cacheline + size = (size+31)&~31; + + if ( hunk_low.temp + hunk_high.temp + size > s_hunkTotal ) { +#ifdef HUNK_DEBUG + Hunk_Log(); + Hunk_SmallLog(); + + Com_Error(ERR_DROP, "Hunk_Alloc failed on %i: %s, line: %d (%s)", size, file, line, label); +#else + Com_Error(ERR_DROP, "Hunk_Alloc failed on %i", size); +#endif + } + + if ( hunk_permanent == &hunk_low ) { + buf = (void *)(s_hunkData + hunk_permanent->permanent); + hunk_permanent->permanent += size; + } else { + hunk_permanent->permanent += size; + buf = (void *)(s_hunkData + s_hunkTotal - hunk_permanent->permanent ); + } + + hunk_permanent->temp = hunk_permanent->permanent; + + ::memset( buf, 0, size ); + +#ifdef HUNK_DEBUG + { + hunkblock_t *block; + + block = (hunkblock_t *) buf; + block->size = size - sizeof(hunkblock_t); + block->file = file; + block->label = label; + block->line = line; + block->next = hunkblocks; + hunkblocks = block; + buf = ((byte *) buf) + sizeof(hunkblock_t); + } +#endif + return buf; +} + +/* +================= +Hunk_AllocateTempMemory + +This is used by the file loading system. +Multiple files can be loaded in temporary memory. +When the files-in-use count reaches zero, all temp memory will be deleted +================= +*/ +void *Hunk_AllocateTempMemory( int size ) +{ + void* buf; + hunkHeader_t* hdr; + + // return a Z_Malloc'd block if the hunk has not been initialized + // this allows the config and product id files ( journal files too ) to be loaded + // by the file system without redunant routines in the file system utilizing different + // memory systems + if ( s_hunkData == NULL ) + return Z_Malloc(size); + + Hunk_SwapBanks(); + + size = PAD(size, sizeof(intptr_t)) + sizeof( hunkHeader_t ); + + if ( hunk_temp->temp + hunk_permanent->permanent + size > s_hunkTotal ) + Com_Error( ERR_DROP, "Hunk_AllocateTempMemory: failed on %i", size ); + + if ( hunk_temp == &hunk_low ) + { + buf = (void *)(s_hunkData + hunk_temp->temp); + hunk_temp->temp += size; + } + else + { + hunk_temp->temp += size; + buf = (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ); + } + + if ( hunk_temp->temp > hunk_temp->tempHighwater ) + hunk_temp->tempHighwater = hunk_temp->temp; + + hdr = (hunkHeader_t *)buf; + buf = (void *)(hdr+1); + + hdr->magic = HUNK_MAGIC; + hdr->size = size; + + // don't bother clearing, because we are going to load a file over it + return buf; +} + +/* +================== +Hunk_FreeTempMemory +================== +*/ +void Hunk_FreeTempMemory( void *buf ) +{ + // free with Z_Free if the hunk has not been initialized + // this allows the config and product id files ( journal files too ) to be + // loaded by the file system without redunant routines in the file system + // utilizing different memory systems + if ( s_hunkData == NULL ) + { + Z_Free(buf); + return; + } + + hunkHeader_t* hdr = ( (hunkHeader_t *)buf ) - 1; + if ( hdr->magic != HUNK_MAGIC ) + Com_Error(ERR_FATAL, "Hunk_FreeTempMemory: bad magic"); + + hdr->magic = HUNK_FREE_MAGIC; + + // this only works if the files are freed in stack order, + // otherwise the memory will stay around until Hunk_ClearTempMemory + if ( hunk_temp == &hunk_low ) + { + if ( hdr == (void *)(s_hunkData + hunk_temp->temp - hdr->size ) ) + hunk_temp->temp -= hdr->size; + else + Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); + } + else + { + if ( hdr == (void *)(s_hunkData + s_hunkTotal - hunk_temp->temp ) ) + hunk_temp->temp -= hdr->size; + else + Com_Printf( "Hunk_FreeTempMemory: not the final block\n" ); + } +} + +/* +================= +Hunk_ClearTempMemory + +The temp space is no longer needed. If we have left more +touched but unused memory on this side, have future +permanent allocs use this side. +================= +*/ +void Hunk_ClearTempMemory( void ) +{ + if ( s_hunkData != NULL ) + hunk_temp->temp = hunk_temp->permanent; +} + +/* +=================================================================== + +EVENTS AND JOURNALING + +In addition to these events, .cfg files are also copied to the +journaled file +=================================================================== +*/ + +#define MAX_PUSHED_EVENTS 1024 +static int com_pushedEventsHead = 0; +static int com_pushedEventsTail = 0; +static sysEvent_t com_pushedEvents[MAX_PUSHED_EVENTS]; + +/* +================= +Com_InitJournaling +================= +*/ +void Com_InitJournaling( void ) +{ + Com_StartupVariable( "journal" ); + com_journal = Cvar_Get ("journal", "0", CVAR_INIT); + if ( !com_journal->integer ) { + return; + } + + if ( com_journal->integer == 1 ) { + Com_Printf( "Journaling events\n"); + com_journalFile = FS_FOpenFileWrite( "journal.dat" ); + com_journalDataFile = FS_FOpenFileWrite( "journaldata.dat" ); + } else if ( com_journal->integer == 2 ) { + Com_Printf( "Replaying journaled events\n"); + FS_FOpenFileRead( "journal.dat", &com_journalFile, true ); + FS_FOpenFileRead( "journaldata.dat", &com_journalDataFile, true ); + } + + if ( !com_journalFile || !com_journalDataFile ) { + Cvar_Set( "com_journal", "0" ); + com_journalFile = 0; + com_journalDataFile = 0; + Com_Printf( "Couldn't open journal files\n" ); + } +} + +/* +======================================================================== + +EVENT LOOP + +======================================================================== +*/ + +#define MAX_QUEUED_EVENTS 256 +#define MASK_QUEUED_EVENTS ( MAX_QUEUED_EVENTS - 1 ) + +static sysEvent_t eventQueue[ MAX_QUEUED_EVENTS ]; +static int eventHead = 0; +static int eventTail = 0; + +/* +================ +Com_QueueEvent + +A time of 0 will get the current time +Ptr should either be null, or point to a block of data that can +be freed by the game later. +================ +*/ +void Com_QueueEvent( int time, sysEventType_t type, int value, int value2, int ptrLength, void *ptr ) +{ + sysEvent_t *ev; + + // combine mouse movement with previous mouse event + if ( type == SE_MOUSE && eventHead != eventTail ) + { + ev = &eventQueue[ ( eventHead + MAX_QUEUED_EVENTS - 1 ) & MASK_QUEUED_EVENTS ]; + + if ( ev->evType == SE_MOUSE ) + { + ev->evValue += value; + ev->evValue2 += value2; + return; + } + } + + ev = &eventQueue[ eventHead & MASK_QUEUED_EVENTS ]; + + if ( eventHead - eventTail >= MAX_QUEUED_EVENTS ) + { + Com_Printf("Com_QueueEvent: overflow\n"); + // we are discarding an event, but don't leak memory + if ( ev->evPtr ) + { + Z_Free( ev->evPtr ); + } + eventTail++; + } + + eventHead++; + + if ( time == 0 ) + { + time = Sys_Milliseconds(); + } + + ev->evTime = time; + ev->evType = type; + ev->evValue = value; + ev->evValue2 = value2; + ev->evPtrLength = ptrLength; + ev->evPtr = ptr; +} + +/* +================ +Com_GetSystemEvent + +================ +*/ +sysEvent_t Com_GetSystemEvent( void ) +{ + sysEvent_t ev; + char *s; + + // return if we have data + if ( eventHead > eventTail ) + { + eventTail++; + return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ]; + } + + // check for console commands + s = Sys_ConsoleInput(); + if ( s ) + { + char *b; + int len; + + len = strlen( s ) + 1; + b = (char*)Z_Malloc( len ); + strcpy( b, s ); + Com_QueueEvent( 0, SE_CONSOLE, 0, 0, len, b ); + } + + // return if we have data + if ( eventHead > eventTail ) + { + eventTail++; + return eventQueue[ ( eventTail - 1 ) & MASK_QUEUED_EVENTS ]; + } + + // create an empty event to return + memset( &ev, 0, sizeof( ev ) ); + ev.evTime = Sys_Milliseconds(); + + return ev; +} + +/* +================= +Com_GetRealEvent +================= +*/ +sysEvent_t Com_GetRealEvent( void ) +{ + sysEvent_t ev; + + // either get an event from the system or the journal file + if ( com_journal->integer == 2 ) + { + int r = FS_Read( &ev, sizeof(ev), com_journalFile ); + if ( r != sizeof(ev) ) + Com_Error( ERR_FATAL, "Error reading from journal file" ); + + if ( ev.evPtrLength ) + { + ev.evPtr = Z_Malloc( ev.evPtrLength ); + r = FS_Read( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) { + Com_Error( ERR_FATAL, "Error reading from journal file" ); + } + } + } + else + { + ev = Com_GetSystemEvent(); + + // write the journal value out if needed + if ( com_journal->integer == 1 ) + { + int r = FS_Write( &ev, sizeof(ev), com_journalFile ); + if ( r != sizeof(ev) ) + Com_Error( ERR_FATAL, "Error writing to journal file" ); + + if ( ev.evPtrLength ) + { + r = FS_Write( ev.evPtr, ev.evPtrLength, com_journalFile ); + if ( r != ev.evPtrLength ) + Com_Error( ERR_FATAL, "Error writing to journal file" ); + } + } + } + + return ev; +} + + +/* +================= +Com_InitPushEvent +================= +*/ +void Com_InitPushEvent( void ) { + // clear the static buffer array + // this requires SE_NONE to be accepted as a valid but NOP event + memset( com_pushedEvents, 0, sizeof(com_pushedEvents) ); + // reset counters while we are at it + // beware: GetEvent might still return an SE_NONE from the buffer + com_pushedEventsHead = 0; + com_pushedEventsTail = 0; +} + + +/* +================= +Com_PushEvent +================= +*/ +void Com_PushEvent( sysEvent_t *event ) +{ + static int printedWarning = 0; + sysEvent_t *ev = &com_pushedEvents[ com_pushedEventsHead & (MAX_PUSHED_EVENTS-1) ]; + + if ( com_pushedEventsHead - com_pushedEventsTail >= MAX_PUSHED_EVENTS ) + { + // don't print the warning constantly, or it can give time for more... + if ( !printedWarning ) + { + printedWarning = true; + Com_Printf("WARNING: Com_PushEvent overflow\n"); + } + + if ( ev->evPtr ) + Z_Free( ev->evPtr ); + + com_pushedEventsTail++; + } + else + { + printedWarning = false; + } + + *ev = *event; + com_pushedEventsHead++; +} + +/* +================= +Com_GetEvent +================= +*/ +sysEvent_t Com_GetEvent(void) +{ + if ( com_pushedEventsHead > com_pushedEventsTail ) + { + com_pushedEventsTail++; + return com_pushedEvents[ (com_pushedEventsTail-1) & (MAX_PUSHED_EVENTS-1) ]; + } + return Com_GetRealEvent(); +} + +/* +================= +Com_RunAndTimeServerPacket +================= +*/ +void Com_RunAndTimeServerPacket( netadr_t *evFrom, msg_t *buf ) +{ + int t1 = 0; + if ( com_speeds->integer ) + t1 = Sys_Milliseconds(); + + SV_PacketEvent(*evFrom, buf); + + if ( com_speeds->integer ) + { + int t2 = Sys_Milliseconds(); + int msec = t2 - t1; + if ( com_speeds->integer == 3 ) + Com_Printf("SV_PacketEvent time: %i\n", msec); + } +} + +/* +================= +Com_EventLoop + +Returns last event time +================= +*/ +int Com_EventLoop(void) +{ + sysEvent_t ev; + netadr_t evFrom; + byte bufData[MAX_MSGLEN]; + msg_t buf; + + MSG_Init(&buf, bufData, sizeof(bufData)); + + for (;;) + { + ev = Com_GetEvent(); + + // if no more events are available + if ( ev.evType == SE_NONE ) + { + // manually send packet events for the loopback channel + while ( NET_GetLoopPacket( NS_CLIENT, &evFrom, &buf ) ) + CL_PacketEvent( evFrom, &buf ); + + // if the server just shut down, flush the events + while ( NET_GetLoopPacket( NS_SERVER, &evFrom, &buf ) ) + if ( com_sv_running->integer ) + Com_RunAndTimeServerPacket( &evFrom, &buf ); + + return ev.evTime; + } + + switch(ev.evType) + { + case SE_KEY: + CL_KeyEvent( ev.evValue, (bool)ev.evValue2, ev.evTime ); + break; + case SE_CHAR: + CL_CharEvent( ev.evValue ); + break; + case SE_MOUSE: + CL_MouseEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_JOYSTICK_AXIS: + CL_JoystickEvent( ev.evValue, ev.evValue2, ev.evTime ); + break; + case SE_CONSOLE: + Cbuf_AddText( (char *)ev.evPtr ); + Cbuf_AddText( "\n" ); + break; + default: + Com_Error( ERR_FATAL, "Com_EventLoop: bad event type %i", ev.evType ); + break; + } + + // free any block data + if ( ev.evPtr ) + Z_Free( ev.evPtr ); + } + + return 0; // never reached +} + +/* +================ +Com_Milliseconds + +Can be used for profiling, but will be journaled accurately +================ +*/ +int Com_Milliseconds(void) +{ + sysEvent_t ev; + // get events and push them until we get a null event with the current time + do { + ev = Com_GetRealEvent(); + + if ( ev.evType != SE_NONE ) + Com_PushEvent( &ev ); + + } while ( ev.evType != SE_NONE ); + + return ev.evTime; +} + +//============================================================================ + +/* +============= +Com_Error_f + +Just throw a fatal error to +test error shutdown procedures +============= +*/ +static void __attribute__((__noreturn__)) Com_Error_f (void) +{ + if ( Cmd_Argc() > 1 ) + Com_Error( ERR_DROP, "Testing drop error" ); + else + Com_Error( ERR_FATAL, "Testing fatal error" ); +} + +/* +============= +Com_Freeze_f + +Just freeze in place for a given number of seconds to test +error recovery +============= +*/ +static void Com_Freeze_f (void) +{ + float s; + int start, now; + + if ( Cmd_Argc() != 2 ) + { + Com_Printf( "freeze \n" ); + return; + } + + s = atof(Cmd_Argv(1)); + start = Com_Milliseconds(); + + for (;;) + { + now = Com_Milliseconds(); + if ( (now - start) * 0.001 > s ) + break; + } +} + +/* +================= +Com_Crash_f + +A way to force a bus error for development reasons +================= +*/ +static void Com_Crash_f( void ) +{ + *( volatile int * )0 = 0x12345678; +} + +/* +================== +Com_ExecuteCfg + +For controlling environment variables +================== +*/ + +void Com_ExecuteCfg(void) +{ + Cbuf_ExecuteText(EXEC_NOW, "exec default.cfg\n"); + Cbuf_Execute(); // Always execute after exec to prevent text buffer overflowing + + if(!Com_SafeMode()) + { + // skip the q3config.cfg and autoexec.cfg if "safe" is on the command line + Cbuf_ExecuteText(EXEC_NOW, "exec " Q3CONFIG_CFG "\n"); + Cbuf_Execute(); + Cbuf_ExecuteText(EXEC_NOW, "exec autoexec.cfg\n"); + Cbuf_Execute(); + } +} + +/* +================== +Com_GameRestart + +Change to a new mod properly with cleaning up cvars before switching. +================== +*/ + +void Com_GameRestart(int checksumFeed, bool disconnect) +{ + // make sure no recursion can be triggered + if(!com_gameRestarting && com_fullyInitialized) + { + int clWasRunning; + + com_gameRestarting = true; + clWasRunning = com_cl_running->integer; + + // Kill server if we have one + if(com_sv_running->integer) + SV_Shutdown("Game directory changed"); + + if(clWasRunning) + { + if(disconnect) + CL_Disconnect(false); + + CL_Shutdown("Game directory changed", disconnect, false); + } + + FS_Restart(checksumFeed); + + // Clean out any user and VM created cvars + Cvar_Restart(true); + Com_ExecuteCfg(); + + if(disconnect) + { + // We don't want to change any network settings if gamedir + // change was triggered by a connect to server because the + // new network settings might make the connection fail. + NET_Restart_f(); + } + + if(clWasRunning) + { + CL_Init(); + CL_StartHunkUsers(false); + } + + com_gameRestarting = false; + } +} + +/* +================== +Com_GameRestart_f + +Expose possibility to change current running mod to the user +================== +*/ + +void Com_GameRestart_f(void) +{ + if(!FS_FilenameCompare(Cmd_Argv(1), BASEGAME)) + { + // This is the standard base game. Servers and clients should + // use "" and not the standard basegame name because this messes + // up pak file negotiation and lots of other stuff + + Cvar_Set("fs_game", ""); + } + else + Cvar_Set("fs_game", Cmd_Argv(1)); + + Com_GameRestart(0, true); +} + +static void Com_DetectAltivec(void) +{ + // Only detect if user hasn't forcibly disabled it. + if ( com_altivec->integer ) + { + static bool altivec = false; + static bool detected = false; + + if (!detected) + { + altivec = ( Sys_GetProcessorFeatures( ) & CF_ALTIVEC ) == CF_ALTIVEC; + detected = true; + } + + if (!altivec) + Cvar_Set( "com_altivec", "0" ); // we don't have it! Disable support! + } +} + +/* +================= +Com_DetectSSE +Find out whether we have SSE support +================= +*/ + +#if id386 || idx64 +static void Com_DetectSSE(void) +{ +#if !idx64 + cpuFeatures_t feat = Sys_GetProcessorFeatures(); + if(feat & CF_SSE) + { + if(feat & CF_SSE2) + Q_SnapVector = qsnapvectorsse; + else + Q_SnapVector = qsnapvectorx87; +#endif + Com_Printf("Have SSE support\n"); +#if !idx64 + } + else + { + Q_SnapVector = qsnapvectorx87; + + Com_Printf("No SSE support on this machine\n"); + } +#endif +} + +#else + +#define Com_DetectSSE() + +#endif + +/* +================= +Com_InitRand +Seed the random number generator, if possible with an OS supplied random seed. +================= +*/ +static void Com_InitRand(void) +{ + unsigned int seed; + + if(Sys_RandomBytes((byte *) &seed, sizeof(seed))) + srand(seed); + else + srand(time(NULL)); +} + +/* +================= +Com_Init +================= +*/ +void Com_Init( char *commandLine ) +{ + int qport; + + if ( setjmp (abortframe) ) { + Sys_Error ("Error during initialization"); + } + + // Clear queues + ::memset( &eventQueue[ 0 ], 0, MAX_QUEUED_EVENTS * sizeof( sysEvent_t ) ); + + // initialize the weak pseudo-random number generator for use later. + Com_InitRand(); + + // do this before anything else decides to push events + Com_InitPushEvent(); + + Com_InitSmallZoneMemory(); + Cvar_Init(); + + // prepare enough of the subsystems to handle + // cvar and command buffer management + Com_ParseCommandLine( commandLine ); + + //Swap_Init (); + Cbuf_Init (); + + Com_DetectSSE(); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + Com_InitZoneMemory(); + Cmd_Init (); + + // get the developer cvar set as early as possible + com_developer = Cvar_Get("developer", "0", CVAR_TEMP); + + // done early so bind command exists + CL_InitKeyCommands(); + + com_homepath = Cvar_Get("com_homepath", "", CVAR_INIT); + + FS_InitFilesystem (); + + Com_InitJournaling(); + + // Add some commands here already so users can use them from config files + if (com_developer && com_developer->integer) + { + Cmd_AddCommand ("error", Com_Error_f); + Cmd_AddCommand ("crash", Com_Crash_f); + Cmd_AddCommand ("freeze", Com_Freeze_f); + } + Cmd_AddCommand ("quit", Com_Quit_f); + Cmd_AddCommand ("changeVectors", MSG_ReportChangeVectors_f ); + Cmd_AddCommand ("writeconfig", Com_WriteConfig_f ); + Cmd_SetCommandCompletionFunc( "writeconfig", Cmd_CompleteCfgName ); + Cmd_AddCommand("game_restart", Com_GameRestart_f); + + Com_ExecuteCfg(); + + // override anything from the config files with command line args + Com_StartupVariable( NULL ); + + // get dedicated here for proper hunk megs initialization +#ifdef DEDICATED + com_dedicated = Cvar_Get ("dedicated", "1", CVAR_INIT); + Cvar_CheckRange( com_dedicated, 1, 2, true ); +#else + com_dedicated = Cvar_Get ("dedicated", "0", CVAR_LATCH); + Cvar_CheckRange( com_dedicated, 0, 2, true ); +#endif + // allocate the stack based hunk allocator + Com_InitHunkMemory(); + + // if any archived cvars are modified after this, we will trigger a writing + // of the config file + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + // + // init commands and vars + // + com_altivec = Cvar_Get ("com_altivec", "1", CVAR_ARCHIVE); + com_maxfps = Cvar_Get ("com_maxfps", "85", CVAR_ARCHIVE); + + com_logfile = Cvar_Get ("logfile", "0", CVAR_TEMP ); + + com_timescale = Cvar_Get ("timescale", "1", CVAR_CHEAT | CVAR_SYSTEMINFO ); + com_fixedtime = Cvar_Get ("fixedtime", "0", CVAR_CHEAT); + com_showtrace = Cvar_Get ("com_showtrace", "0", CVAR_CHEAT); + com_speeds = Cvar_Get ("com_speeds", "0", 0); + com_timedemo = Cvar_Get ("timedemo", "0", CVAR_CHEAT); + com_cameraMode = Cvar_Get ("com_cameraMode", "0", CVAR_CHEAT); + + cl_paused = Cvar_Get ("cl_paused", "0", CVAR_ROM); + sv_paused = Cvar_Get ("sv_paused", "0", CVAR_ROM); + cl_packetdelay = Cvar_Get ("cl_packetdelay", "0", CVAR_CHEAT); + sv_packetdelay = Cvar_Get ("sv_packetdelay", "0", CVAR_CHEAT); + com_sv_running = Cvar_Get ("sv_running", "0", CVAR_ROM); + com_cl_running = Cvar_Get ("cl_running", "0", CVAR_ROM); + com_buildScript = Cvar_Get( "com_buildScript", "0", 0 ); + com_ansiColor = Cvar_Get( "com_ansiColor", "0", CVAR_ARCHIVE ); + + com_unfocused = Cvar_Get( "com_unfocused", "0", CVAR_ROM ); + com_maxfpsUnfocused = Cvar_Get( "com_maxfpsUnfocused", "0", CVAR_ARCHIVE ); + com_minimized = Cvar_Get( "com_minimized", "0", CVAR_ROM ); + com_maxfpsMinimized = Cvar_Get( "com_maxfpsMinimized", "0", CVAR_ARCHIVE ); + com_busyWait = Cvar_Get("com_busyWait", "0", CVAR_ARCHIVE); + Cvar_Get("com_errorMessage", "", CVAR_ROM | CVAR_NORESTART); + Cvar_Get("com_demoErrorMessage", "", CVAR_ROM | CVAR_NORESTART); + + com_version = Cvar_Get ("version", PRODUCT_NAME, CVAR_ROM | CVAR_SERVERINFO ); + Cvar_Get ("protocol", va("%i", PROTOCOL_VERSION), CVAR_SERVERINFO | CVAR_ROM); + com_gamename = Cvar_Get("com_gamename", GAMENAME_FOR_MASTER, CVAR_SERVERINFO | CVAR_INIT); + + Sys_Init(); + + // Pick a random port value + Com_RandomBytes( (byte*)&qport, sizeof(int) ); + Netchan_Init( qport & 0xffff ); + + VM_Init(); + Crypto_Init(); + SV_Init(); + + com_dedicated->modified = false; +#ifndef DEDICATED + CL_Init(); +#endif + + // set com_frameTime so that if a map is started on the + // command line it will still be able to count on com_frameTime + // being random enough for a serverid + com_frameTime = Com_Milliseconds(); + + // add + commands from command line + if ( !Com_AddStartupCommands() ) { +#ifdef CINEMATICS_LOGO + // if the user didn't give any commands, run default action + if ( !com_dedicated->integer ) { + Cbuf_AddText ("cinematic splash.RoQ\n"); + } +#endif + } + + // start in full screen ui mode + Cvar_Set("r_uiFullScreen", "1"); + + CL_StartHunkUsers( false ); + + com_fullyInitialized = true; + + // always set the cvar, but only print the info if it makes sense. + Com_DetectAltivec(); +#if idppc + Com_Printf ("Altivec support is %s\n", com_altivec->integer ? "enabled" : "disabled"); +#endif + + com_pipefile = Cvar_Get( "com_pipefile", "", CVAR_ARCHIVE|CVAR_LATCH ); + if( com_pipefile->string[0] ) + { + pipefile = FS_FCreateOpenPipeFile( com_pipefile->string ); + } + + Com_Printf ("--- Common Initialization Complete ---\n"); +} + +/* +=============== +Com_ReadFromPipe + +Read whatever is in com_pipefile, if anything, and execute it +=============== +*/ +void Com_ReadFromPipe( void ) +{ + static char buf[MAX_STRING_CHARS]; + static int accu = 0; + int read; + + if( !pipefile ) + return; + + while( ( read = FS_Read( buf + accu, sizeof( buf ) - accu - 1, pipefile ) ) > 0 ) + { + char *brk = NULL; + + for( int i = accu; i < accu + read; ++i ) + { + if( buf[ i ] == '\0' ) + buf[ i ] = '\n'; + if( buf[ i ] == '\n' || buf[ i ] == '\r' ) + brk = &buf[ i + 1 ]; + } + buf[ accu + read ] = '\0'; + + accu += read; + + if( brk ) + { + char tmp = *brk; + *brk = '\0'; + Cbuf_ExecuteText( EXEC_APPEND, buf ); + *brk = tmp; + + accu -= brk - buf; + memmove( buf, brk, accu + 1 ); + } + else if( accu >= sizeof( buf ) - 1 ) // full + { + Cbuf_ExecuteText( EXEC_APPEND, buf ); + accu = 0; + } + } +} + + +//================================================================== + +void Com_WriteConfigToFile( const char *filename ) +{ + fileHandle_t f; + + f = FS_FOpenFileWrite( filename ); + if ( !f ) + { + Com_Printf("Couldn't write %s.\n", filename ); + return; + } + + FS_Printf(f, "// generated by tremulous, do not modify\n"); + + Key_WriteBindings(f); + Cvar_WriteVariables(f); + FS_FCloseFile(f); +} + + +/* +=============== +Com_WriteConfiguration + +Writes key bindings and archived cvars to config file if modified +=============== +*/ +void Com_WriteConfiguration( void ) +{ + // if we are quiting without fully initializing, make sure + // we don't write out anything + if ( !com_fullyInitialized ) + return; + + if ( !(cvar_modifiedFlags & CVAR_ARCHIVE) ) + return; + + cvar_modifiedFlags &= ~CVAR_ARCHIVE; + + Com_WriteConfigToFile(Q3CONFIG_CFG); +} + +/* +=============== +Com_WriteConfig_f + +Write the config file to a specific name +=============== +*/ +void Com_WriteConfig_f( void ) +{ + char filename[MAX_QPATH]; + + if ( Cmd_Argc() != 2 ) { + Com_Printf( "Usage: writeconfig \n" ); + return; + } + + Q_strncpyz( filename, Cmd_Argv(1), sizeof( filename ) ); + COM_DefaultExtension( filename, sizeof( filename ), ".cfg" ); + + if ( !COM_CompareExtension(filename, ".cfg") ) + { + Com_Printf("Com_WriteConfig_f: Only the \".cfg\" extension is supported by this command!\n"); + return; + } + + Com_Printf( "Writing %s.\n", filename ); + Com_WriteConfigToFile( filename ); +} + +/* +================ +Com_ModifyMsec +================ +*/ +int Com_ModifyMsec( int msec ) +{ + int clampTime; + + // + // modify time for debugging values + // + if ( com_fixedtime->integer ) + msec = com_fixedtime->integer; + else if ( com_timescale->value ) + msec *= com_timescale->value; + else if (com_cameraMode->integer) + msec *= com_timescale->value; + + // don't let it scale below 1 msec + if ( msec < 1 && com_timescale->value) + msec = 1; + + if ( com_dedicated->integer ) + { + // dedicated servers don't want to clamp for a much longer + // period, because it would mess up all the client's views + // of time. + if (com_sv_running->integer && msec > 500) + Com_Printf("Hitch warning: %i msec frame time\n", msec); + + clampTime = 5000; + } + else + { + if ( !com_sv_running->integer ) + { + // clients of remote servers do not want to clamp time, because + // it would skew their view of the server's time temporarily + clampTime = 5000; + } + else + { + // for local single player gaming + // we may want to clamp the time to prevent players from + // flying off edges when something hitches. + clampTime = 200; + } + } + + if ( msec > clampTime ) + msec = clampTime; + + return msec; +} + +/* +================= +Com_TimeVal +================= +*/ + +int Com_TimeVal(int minMsec) +{ + int timeVal = Sys_Milliseconds() - com_frameTime; + + if(timeVal >= minMsec) + timeVal = 0; + else + timeVal = minMsec - timeVal; + + return timeVal; +} + +/* +================= +Com_Frame +================= +*/ +void Com_Frame( void ) +{ + + int msec, minMsec; + int timeVal, timeValSV; + static int lastTime = 0, bias = 0; + + int timeBeforeFirstEvents; + int timeBeforeServer; + int timeBeforeEvents; + int timeBeforeClient; + int timeAfter; + + if ( setjmp(abortframe) ) + return; // an ERR_DROP was thrown + + timeBeforeFirstEvents =0; + timeBeforeServer =0; + timeBeforeEvents =0; + timeBeforeClient = 0; + timeAfter = 0; + + // write config file if anything changed + Com_WriteConfiguration(); + + // + // main event loop + // + if ( com_speeds->integer ) + timeBeforeFirstEvents = Sys_Milliseconds(); + + // Figure out how much time we have + if ( !com_timedemo->integer ) + { + if(com_dedicated->integer) + { + minMsec = SV_FrameMsec(); + } + else + { + if(com_minimized->integer && com_maxfpsMinimized->integer > 0) + minMsec = 1000 / com_maxfpsMinimized->integer; + else if(com_unfocused->integer && com_maxfpsUnfocused->integer > 0) + minMsec = 1000 / com_maxfpsUnfocused->integer; + else if(com_maxfps->integer > 0) + minMsec = 1000 / com_maxfps->integer; + else + minMsec = 1; + + timeVal = com_frameTime - lastTime; + bias += timeVal - minMsec; + + if(bias > minMsec) + bias = minMsec; + + // Adjust minMsec if previous frame took too long to render so + // that framerate is stable at the requested value. + minMsec -= bias; + } + } + else + { + minMsec = 1; + } + + do { + if ( com_sv_running->integer ) + { + timeValSV = SV_SendQueuedPackets(); + timeVal = Com_TimeVal(minMsec); + + if ( timeValSV < timeVal ) + timeVal = timeValSV; + } + else + { + timeVal = Com_TimeVal(minMsec); + } + + if ( com_busyWait->integer || timeVal < 1 ) + NET_Sleep(0); + else + NET_Sleep(timeVal - 1); + } while( Com_TimeVal(minMsec) ); + + IN_Frame(); + + lastTime = com_frameTime; + com_frameTime = Com_EventLoop(); + + msec = com_frameTime - lastTime; + + Cbuf_Execute(); + + if ( com_altivec->modified ) + { + Com_DetectAltivec(); + com_altivec->modified = false; + } + + // mess with msec if needed + msec = Com_ModifyMsec(msec); + + // + // server side + // + if ( com_speeds->integer ) + timeBeforeServer = Sys_Milliseconds(); + + SV_Frame(msec); + + // if "dedicated" has been modified, start up + // or shut down the client system. + // Do this after the server may have started, + // but before the client tries to auto-connect + if ( com_dedicated->modified ) + { + // get the latched value + Cvar_Get("dedicated", "0", 0); + com_dedicated->modified = false; + + if ( !com_dedicated->integer ) + { + SV_Shutdown("dedicated set to 0"); + CL_FlushMemory(); + } + } + +#ifndef DEDICATED + // + // client system + // + // + // run event loop a second time to get server to client packets + // without a frame of latency + // + if ( com_speeds->integer ) + timeBeforeEvents = Sys_Milliseconds(); + + Com_EventLoop(); + Cbuf_Execute(); + + // + // client side + // + if ( com_speeds->integer ) + timeBeforeClient = Sys_Milliseconds(); + + CL_Frame(msec); + + if ( com_speeds->integer ) + timeAfter = Sys_Milliseconds(); +#else + if ( com_speeds->integer ) + { + timeAfter = Sys_Milliseconds(); + timeBeforeEvents = timeAfter; + timeBeforeClient = timeAfter; + } +#endif + + NET_FlushPacketQueue(); + + // + // report timing information + // + if ( com_speeds->integer ) + { + int all = timeAfter - timeBeforeServer; + int sv = timeBeforeEvents - timeBeforeServer; + int ev = timeBeforeServer - timeBeforeFirstEvents + timeBeforeClient - timeBeforeEvents; + int cl = timeAfter - timeBeforeClient; + + sv -= time_game; + cl -= time_frontend + time_backend; + + Com_Printf("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n", + com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend ); + } + + // + // trace optimization tracking + // + if ( com_showtrace->integer ) + { + extern int c_traces, c_brush_traces, c_patch_traces; + extern int c_pointcontents; + + Com_Printf("%4i traces (%ib %ip) %4i points\n", + c_traces, c_brush_traces, c_patch_traces, c_pointcontents); + + c_traces = 0; + c_brush_traces = 0; + c_patch_traces = 0; + c_pointcontents = 0; + } + + Com_ReadFromPipe(); + + com_frameNumber++; +} + +/* +================= +Com_Shutdown +================= +*/ +void Com_Shutdown(void) +{ + if (logfile) + { + FS_FCloseFile (logfile); + logfile = 0; + } + + if ( com_journalFile ) + { + FS_FCloseFile( com_journalFile ); + com_journalFile = 0; + } + + if( pipefile ) + { + FS_FCloseFile( pipefile ); + FS_HomeRemove( com_pipefile->string ); + } +} + +/* +=========================================== +command line completion +=========================================== +*/ + +/* +================== +Field_Clear +================== +*/ +void Field_Clear( field_t *edit ) +{ + memset(edit->buffer, 0, MAX_EDIT_LINE); + edit->cursor = 0; + edit->scroll = 0; +} + +static const char *completionString; +static char shortestMatch[MAX_TOKEN_CHARS]; +static int matchCount; +// field we are working on, passed to Field_AutoComplete(&g_consoleCommand for instance) +static field_t *completionField; + +/* +=============== +FindMatches + +=============== +*/ +static void FindMatches( const char *s ) +{ + if ( Q_stricmpn(s, completionString, strlen(completionString)) ) + return; + + matchCount++; + if ( matchCount == 1 ) + { + Q_strncpyz( shortestMatch, s, sizeof( shortestMatch ) ); + return; + } + + // cut shortestMatch to the amount common with s + for ( int i = 0 ; shortestMatch[i] ; i++ ) + { + if ( i >= strlen(s) ) + { + shortestMatch[i] = 0; + break; + } + + if ( tolower(shortestMatch[i]) != tolower(s[i]) ) + shortestMatch[i] = 0; + } +} + +/* +=============== +PrintMatches + +=============== +*/ +static void PrintMatches( const char *s ) +{ + if ( !Q_stricmpn(s, shortestMatch, strlen(shortestMatch)) ) + Com_Printf(" %s\n", s); +} + +/* +=============== +PrintCvarMatches + +=============== +*/ +static void PrintCvarMatches( const char *s ) +{ + char value[ TRUNCATE_LENGTH ]; + if ( !Q_stricmpn(s, shortestMatch, strlen( shortestMatch)) ) + { + Com_TruncateLongString( value, Cvar_VariableString( s ) ); + Com_Printf( " %s = \"%s\"\n", s, value ); + } +} + +/* +=============== +Field_FindFirstSeparator +=============== +*/ +static char *Field_FindFirstSeparator( char *s ) +{ + for( int i = 0; i < strlen( s ); i++ ) + if( s[ i ] == ';' ) + return &s[ i ]; + + return NULL; +} + +/* +=============== +Field_Complete +=============== +*/ +static bool Field_Complete( void ) +{ + if( matchCount == 0 ) + return true; + + int completionOffset = strlen( completionField->buffer ) - strlen( completionString ); + + Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch, + sizeof( completionField->buffer ) - completionOffset ); + + completionField->cursor = strlen( completionField->buffer ); + + if( matchCount == 1 ) + { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); + completionField->cursor++; + return true; + } + + Com_Printf( "]%s\n", completionField->buffer ); + + return false; +} + +#ifndef DEDICATED +/* +=============== +Field_CompleteKeyname +=============== +*/ +void Field_CompleteKeyname( void ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 0; + + Key_KeynameCompletion( FindMatches ); + + if( !Field_Complete( ) ) + Key_KeynameCompletion( PrintMatches ); +} +#endif + +/* +=============== +Field_CompleteFilename +=============== +*/ +void Field_CompleteFilename( const char *dir, const char *ext, + bool stripExt, bool allowNonPureFilesOnDisk ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 0; + + FS_FilenameCompletion( dir, ext, stripExt, FindMatches, allowNonPureFilesOnDisk ); + + if( !Field_Complete( ) ) + FS_FilenameCompletion( dir, ext, stripExt, PrintMatches, allowNonPureFilesOnDisk ); +} + +/* +============ +Field_ListCompletion +============ +*/ +void Field_ListCompletion( char *listJson, void(*callback)(const char *s) ) +{ + char item[ 256 ]; + const char *arrayPtr; + const char *listEnd = listJson + strlen( listJson ); + + // JSON parse array + for ( arrayPtr = JSON_ArrayGetFirstValue( listJson, listEnd ); + arrayPtr ; + arrayPtr = JSON_ArrayGetNextValue( arrayPtr, listEnd ) ) + { + JSON_ValueGetString( arrayPtr, listEnd, item, 256 ); + callback( item ); + } +} + +/* +=============== +Field_CompleteList + +Completes an arbirary list of JSON encoded items passed from a VM +=============== +*/ +void Field_CompleteList( char *listJson ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 0; + + Field_ListCompletion( listJson, FindMatches ); + + if( !Field_Complete() ) + Field_ListCompletion( listJson, PrintMatches ); +} + +/* +=============== +Field_CompleteCommand +=============== +*/ +void Field_CompleteCommand( char *cmd, bool doCommands, bool doCvars ) +{ + // Skip leading whitespace and quotes + cmd = Com_SkipCharset( cmd, " \"" ); + + Cmd_TokenizeStringIgnoreQuotes( cmd ); + int completionArgument = Cmd_Argc( ); + + // If there is trailing whitespace on the cmd + if( *( cmd + strlen( cmd ) - 1 ) == ' ' ) + { + completionString = ""; + completionArgument++; + } + else + completionString = Cmd_Argv( completionArgument - 1 ); + + if ( completionString == nullptr ) + return; + +#ifndef DEDICATED + // Unconditionally add a '\' to the start of the buffer + if( completionField->buffer[ 0 ] && + completionField->buffer[ 0 ] != '\\' ) + { + if( completionField->buffer[ 0 ] != '/' ) + { + // Buffer is full, refuse to complete + if( strlen( completionField->buffer ) + 1 >= + sizeof( completionField->buffer ) ) + return; + + memmove( &completionField->buffer[ 1 ], + &completionField->buffer[ 0 ], + strlen( completionField->buffer ) + 1 ); + completionField->cursor++; + } + + completionField->buffer[ 0 ] = '\\'; + } +#endif + + if( completionArgument > 1 ) + { + const char *baseCmd = Cmd_Argv( 0 ); + char *p; + +#ifndef DEDICATED + // This should always be true + if( baseCmd[ 0 ] == '\\' || baseCmd[ 0 ] == '/' ) + baseCmd++; +#endif + + if( ( p = Field_FindFirstSeparator( cmd ) ) ) + Field_CompleteCommand( p + 1, true, true ); // Compound command + else + Cmd_CompleteArgument( baseCmd, cmd, completionArgument ); + } + else + { + if( completionString[0] == '\\' || completionString[0] == '/' ) + completionString++; + + matchCount = 0; + shortestMatch[ 0 ] = 0; + + if( strlen( completionString ) == 0 ) + return; + + if( doCommands ) + Cmd_CommandCompletion( FindMatches ); + + if( doCvars ) + Cvar_CommandCompletion( FindMatches ); + + if( !Field_Complete( ) ) + { + // run through again, printing matches + if( doCommands ) + Cmd_CommandCompletion( PrintMatches ); + + if( doCvars ) + Cvar_CommandCompletion( PrintCvarMatches ); + } + } +} + +/* +=============== +Field_AutoComplete + +Perform Tab expansion +=============== +*/ +void Field_AutoComplete( field_t *field ) +{ + completionField = field; + Field_CompleteCommand( completionField->buffer, true, true ); +} + +/* +================== +Com_RandomBytes + +fills string array with len random bytes, preferably from the OS randomizer +================== +*/ +void Com_RandomBytes( byte *string, int len ) +{ + if( Sys_RandomBytes( string, len ) ) + return; + + Com_Printf( "Com_RandomBytes: using weak randomization\n" ); + for( int i = 0; i < len; i++ ) + string[i] = (unsigned char)( rand() % 256 ); +} + + +/* +================== +Com_IsVoipTarget + +Returns non-zero if given clientNum is enabled in voipTargets, zero otherwise. +If clientNum is negative return if any bit is set. +================== +*/ +bool Com_IsVoipTarget(uint8_t *voipTargets, int voipTargetsSize, int clientNum) +{ + int i = 0; + + if ( clientNum < 0 ) + { + for ( i = 0; i < voipTargetsSize; i++ ) + { + if(voipTargets[i]) + return true; + } + + return false; + } + + i = clientNum >> 3; + + if( i < voipTargetsSize ) + return (bool)(voipTargets[i] & (1 << (clientNum & 0x07))); + + return false; +} + +/* +=============== +Field_CompletePlayerName +=============== +*/ +static bool Field_CompletePlayerNameFinal( bool whitespace ) +{ + if( matchCount == 0 ) + return true; + + int completionOffset = strlen( completionField->buffer ) - strlen( completionString ); + + Q_strncpyz( &completionField->buffer[ completionOffset ], shortestMatch, + sizeof( completionField->buffer ) - completionOffset ); + + completionField->cursor = strlen( completionField->buffer ); + + if( matchCount == 1 && whitespace ) + { + Q_strcat( completionField->buffer, sizeof( completionField->buffer ), " " ); + completionField->cursor++; + return true; + } + + return false; +} + +static void Name_PlayerNameCompletion( const char **names, int nameCount, void(*callback)(const char *s) ) +{ + for( int i = 0; i < nameCount; i++ ) + callback( names[ i ] ); +} + +bool Com_FieldStringToPlayerName( char *name, int length, const char *rawname ) +{ + char hex[5]; + + if( name == NULL || rawname == NULL ) + return false; + + if( length <= 0 ) + return true; + + int i; + for( i = 0; *rawname && i + 1 <= length; rawname++, i++ ) + { + if( *rawname == '\\' ) + { + Q_strncpyz( hex, rawname + 1, sizeof(hex) ); + int ch = Com_HexStrToInt( hex ); + if( ch > -1 ) + { + name[i] = ch; + rawname += 4; //hex string length, 0xXX + } + else + { + name[i] = *rawname; + } + } else { + name[i] = *rawname; + } + } + name[i] = '\0'; + + return true; +} + +bool Com_PlayerNameToFieldString( char *str, int length, const char *name ) +{ + const char *p; + int i; + int x1, x2; + + if( str == NULL || name == NULL ) + return false; + + if( length <= 0 ) + return true; + + *str = '\0'; + p = name; + + for( i = 0; *p != '\0'; i++, p++ ) + { + if( i + 1 >= length ) + break; + + if( *p <= ' ' ) + { + if( i + 5 + 1 >= length ) + break; + + x1 = *p >> 4; + x2 = *p & 15; + + str[i+0] = '\\'; + str[i+1] = '0'; + str[i+2] = 'x'; + str[i+3] = x1 > 9 ? x1 - 10 + 'a' : x1 + '0'; + str[i+4] = x2 > 9 ? x2 - 10 + 'a' : x2 + '0'; + + i += 4; + } else { + str[i] = *p; + } + } + str[i] = '\0'; + + return true; +} + +void Field_CompletePlayerName( const char **names, int nameCount ) +{ + + matchCount = 0; + shortestMatch[ 0 ] = 0; + + if( nameCount <= 0 ) + return; + + Name_PlayerNameCompletion( names, nameCount, FindMatches ); + + if( completionString[0] == '\0' ) + Com_PlayerNameToFieldString( shortestMatch, sizeof( shortestMatch ), names[ 0 ] ); + + //allow to tab player names + //if full player name switch to next player name + if( completionString[0] != '\0' + && Q_stricmp( shortestMatch, completionString ) == 0 + && nameCount > 1 ) + { + for( int i = 0; i < nameCount; i++ ) + { + if( Q_stricmp( names[ i ], completionString ) == 0 ) + { + i++; + + if( i >= nameCount ) + i = 0; + + Com_PlayerNameToFieldString( shortestMatch, sizeof( shortestMatch ), names[ i ] ); + break; + } + } + } + + if( matchCount > 1 ) + { + Com_Printf( "]%s\n", completionField->buffer ); + + Name_PlayerNameCompletion( names, nameCount, PrintMatches ); + } + + bool whitespace = nameCount == 1 ? true : false; + Field_CompletePlayerNameFinal(whitespace); +} + +int QDECL Com_strCompare( const void *a, const void *b ) +{ + const char **pa = (const char **)a; + const char **pb = (const char **)b; + return strcmp( *pa, *pb ); +} -- cgit