mirror of
https://github.com/FWGS/xash3d-fwgs.git
synced 2026-08-05 03:24:56 +08:00
engine: client: add new titles.txt parser, that avoids max messages limit
This commit is contained in:
@@ -584,7 +584,7 @@ static void CL_InitTitles( const char *filename )
|
||||
pMemFile = FS_LoadFile( filename, &fileSize, false );
|
||||
if( !pMemFile ) return;
|
||||
|
||||
clgame.titles = CL_TextMessageParse( clgame.mempool, pMemFile, fileSize, &clgame.numTitles );
|
||||
clgame.titles = CL_TextMessageParse( clgame.mempool, (char *)pMemFile, fileSize, &clgame.numTitles );
|
||||
Mem_Free( pMemFile );
|
||||
}
|
||||
|
||||
|
||||
@@ -854,7 +854,6 @@ void CL_ClearWorld( void );
|
||||
void CL_DrawCenterPrint( void );
|
||||
void CL_ClearSpriteTextures( void );
|
||||
void CL_CenterPrint( const char *text, float y );
|
||||
client_textmessage_t *CL_TextMessageParse( poolhandle_t mempool, byte *pMemFile, int fileSize, int *numTitles );
|
||||
client_textmessage_t *CL_TextMessageGet( const char *pName );
|
||||
void NetAPI_CancelAllRequests( void );
|
||||
model_t *CL_LoadClientSprite( const char *filename );
|
||||
@@ -1257,6 +1256,11 @@ qboolean Cmd_GetKeysList( const char *s, char *completedname, int length, qboole
|
||||
void ID_Init( void );
|
||||
void ID_GetMD5ForAddress( char *key, netadr_t adr, size_t size );
|
||||
|
||||
//
|
||||
// titles.c
|
||||
//
|
||||
client_textmessage_t *CL_TextMessageParse( poolhandle_t mempool, char *pMemFile, int fileSize, int *numTitles );
|
||||
|
||||
extern rgba_t g_color_table[8];
|
||||
extern triangleapi_t gTriApi;
|
||||
extern net_api_t gNetApi;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
titles.c - implementation of titles.txt parser
|
||||
titles.c - titles.txt file parser
|
||||
Copyright (C) 2010 Uncle Mike
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
@@ -17,338 +17,200 @@ GNU General Public License for more details.
|
||||
#include "client.h"
|
||||
#include "tests.h"
|
||||
|
||||
#define MAX_MESSAGES 2048
|
||||
client_textmessage_t gMessageParms;
|
||||
|
||||
#define MSGFILE_NAME 0
|
||||
#define MSGFILE_TEXT 1
|
||||
|
||||
client_textmessage_t gMessageParms;
|
||||
|
||||
// the string "pText" is assumed to have all whitespace from both ends cut out
|
||||
static int IsComment( const char *pText )
|
||||
static qboolean TitleParseDirective( const char *line )
|
||||
{
|
||||
if( pText )
|
||||
char directive[64];
|
||||
|
||||
char *p = COM_ParseFile((char *)line, directive, sizeof( directive ));
|
||||
if( !p || directive[0] != '$' )
|
||||
return false;
|
||||
|
||||
int nargs;
|
||||
if( !Q_stricmp( directive, "$position" ))
|
||||
nargs = 2;
|
||||
else if( !Q_stricmp( directive, "$effect" ) || !Q_stricmp( directive, "$fadein" ) ||
|
||||
!Q_stricmp( directive, "$fadeout" ) || !Q_stricmp( directive, "$holdtime" ) ||
|
||||
!Q_stricmp( directive, "$fxtime" ))
|
||||
nargs = 1;
|
||||
else if( !Q_stricmp( directive, "$color" ) || !Q_stricmp( directive, "$color2" ))
|
||||
nargs = 3;
|
||||
else
|
||||
{
|
||||
int length = Q_strlen( pText );
|
||||
|
||||
if( length >= 2 && pText[0] == '/' && pText[1] == '/' )
|
||||
return 1;
|
||||
|
||||
// no text?
|
||||
if( length > 0 )
|
||||
return 0;
|
||||
Con_DPrintf( S_ERROR "unknown directive: %s\n", directive );
|
||||
return true;
|
||||
}
|
||||
|
||||
// no text is a comment too
|
||||
return 1;
|
||||
}
|
||||
|
||||
// the string "pText" is assumed to have all whitespace from both ends cut out
|
||||
static int IsStartOfText( const char *pText )
|
||||
{
|
||||
if( pText )
|
||||
float args[3] = { 0 };
|
||||
for( int i = 0; i < nargs; i++ )
|
||||
{
|
||||
if( pText[0] == '{' )
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
char numstr[32];
|
||||
p = COM_ParseFile( p, numstr, sizeof( numstr ));
|
||||
|
||||
// the string "pText" is assumed to have all whitespace from both ends cut out
|
||||
static int IsEndOfText( const char *pText )
|
||||
{
|
||||
if( pText )
|
||||
{
|
||||
if( pText[0] == '}' )
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if( !p )
|
||||
return true; // not enough arguments
|
||||
|
||||
static int IsWhiteSpace( char space )
|
||||
{
|
||||
if( space == ' ' || space == '\t' || space == '\r' || space == '\n' )
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char *SkipSpace( const char *pText )
|
||||
{
|
||||
if( pText )
|
||||
{
|
||||
int pos = 0;
|
||||
while( pText[pos] && IsWhiteSpace( pText[pos] ))
|
||||
pos++;
|
||||
return pText + pos;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char *SkipText( const char *pText )
|
||||
{
|
||||
if( pText )
|
||||
{
|
||||
int pos = 0;
|
||||
while( pText[pos] && !IsWhiteSpace( pText[pos] ))
|
||||
pos++;
|
||||
return pText + pos;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ParseFloats( const char *pText, float *pFloat, int count )
|
||||
{
|
||||
const char *pTemp = pText;
|
||||
int index = 0;
|
||||
|
||||
while( pTemp && count > 0 )
|
||||
{
|
||||
// skip current token / float
|
||||
pTemp = SkipText( pTemp );
|
||||
// skip any whitespace in between
|
||||
pTemp = SkipSpace( pTemp );
|
||||
|
||||
if( pTemp )
|
||||
{
|
||||
// parse a float
|
||||
pFloat[index] = Q_atof( pTemp );
|
||||
count--;
|
||||
index++;
|
||||
}
|
||||
args[i] = Q_atof( numstr );
|
||||
}
|
||||
|
||||
if( count == 0 )
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int IsToken( const char *pText, const char *pTokenName )
|
||||
{
|
||||
if( !pText || !pTokenName )
|
||||
return 0;
|
||||
|
||||
if( !Q_strnicmp( pText+1, pTokenName, Q_strlen( pTokenName )))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ParseDirective( const char *pText )
|
||||
{
|
||||
if( pText && pText[0] == '$' )
|
||||
if( !Q_stricmp( directive, "$position" ))
|
||||
{
|
||||
float tempFloat[8];
|
||||
|
||||
if( IsToken( pText, "position" ))
|
||||
{
|
||||
if( ParseFloats( pText, tempFloat, 2 ))
|
||||
{
|
||||
gMessageParms.x = tempFloat[0];
|
||||
gMessageParms.y = tempFloat[1];
|
||||
}
|
||||
}
|
||||
else if( IsToken( pText, "effect" ))
|
||||
{
|
||||
if( ParseFloats( pText, tempFloat, 1 ))
|
||||
{
|
||||
gMessageParms.effect = (int)tempFloat[0];
|
||||
}
|
||||
}
|
||||
else if( IsToken( pText, "fxtime" ))
|
||||
{
|
||||
if( ParseFloats( pText, tempFloat, 1 ))
|
||||
{
|
||||
gMessageParms.fxtime = tempFloat[0];
|
||||
}
|
||||
}
|
||||
else if( IsToken( pText, "color2" ))
|
||||
{
|
||||
if( ParseFloats( pText, tempFloat, 3 ))
|
||||
{
|
||||
gMessageParms.r2 = (int)tempFloat[0];
|
||||
gMessageParms.g2 = (int)tempFloat[1];
|
||||
gMessageParms.b2 = (int)tempFloat[2];
|
||||
}
|
||||
}
|
||||
else if( IsToken( pText, "color" ))
|
||||
{
|
||||
if( ParseFloats( pText, tempFloat, 3 ))
|
||||
{
|
||||
gMessageParms.r1 = (int)tempFloat[0];
|
||||
gMessageParms.g1 = (int)tempFloat[1];
|
||||
gMessageParms.b1 = (int)tempFloat[2];
|
||||
}
|
||||
}
|
||||
else if( IsToken( pText, "fadein" ))
|
||||
{
|
||||
if( ParseFloats( pText, tempFloat, 1 ))
|
||||
{
|
||||
gMessageParms.fadein = tempFloat[0];
|
||||
}
|
||||
}
|
||||
else if( IsToken( pText, "fadeout" ))
|
||||
{
|
||||
if( ParseFloats( pText, tempFloat, 3 ))
|
||||
{
|
||||
gMessageParms.fadeout = tempFloat[0];
|
||||
}
|
||||
}
|
||||
else if( IsToken( pText, "holdtime" ))
|
||||
{
|
||||
if( ParseFloats( pText, tempFloat, 3 ))
|
||||
{
|
||||
gMessageParms.holdtime = tempFloat[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Con_DPrintf( S_ERROR "unknown token: %s\n", pText );
|
||||
}
|
||||
return 1;
|
||||
gMessageParms.x = args[0];
|
||||
gMessageParms.y = args[1];
|
||||
}
|
||||
return 0;
|
||||
else if( !Q_stricmp( directive, "$color" ))
|
||||
{
|
||||
gMessageParms.r1 = args[0];
|
||||
gMessageParms.g1 = args[1];
|
||||
gMessageParms.b1 = args[2];
|
||||
}
|
||||
else if( !Q_stricmp( directive, "$color2" ))
|
||||
{
|
||||
gMessageParms.r2 = args[0];
|
||||
gMessageParms.g2 = args[1];
|
||||
gMessageParms.b2 = args[2];
|
||||
}
|
||||
else if( !Q_stricmp( directive, "$effect" ))
|
||||
gMessageParms.effect = args[0];
|
||||
else if( !Q_stricmp( directive, "$fadein" ))
|
||||
gMessageParms.fadein = args[0];
|
||||
else if( !Q_stricmp( directive, "$fadeout" ))
|
||||
gMessageParms.fadeout = args[0];
|
||||
else if( !Q_stricmp( directive, "$holdtime" ))
|
||||
gMessageParms.holdtime = args[0];
|
||||
else if( !Q_stricmp( directive, "$fxtime" ))
|
||||
gMessageParms.fxtime = args[0];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
client_textmessage_t *CL_TextMessageParse( poolhandle_t mempool, byte *pMemFile, int fileSize, int *numTitles )
|
||||
// fast first pass: count the number of complete name { text } blocks
|
||||
static int TitleCountMessages( char *pfile, int fileSize )
|
||||
{
|
||||
char buf[512], trim[512], currentName[512];
|
||||
char *pCurrentText = NULL, *pNameHeap;
|
||||
char nameHeap[32768]; // g-cont. i will scale up heap to handle all TFC messages
|
||||
int mode = MSGFILE_NAME; // searching for a message name
|
||||
int lineNumber, filePos, lastLinePos;
|
||||
client_textmessage_t textMessages[MAX_MESSAGES];
|
||||
int i, nameHeapSize, textHeapSize, messageSize, nameOffset;
|
||||
int messageCount, lastNamePos;
|
||||
size_t textHeapSizeRemaining;
|
||||
char line[512];
|
||||
int filepos = 0, count = 0;
|
||||
qboolean in_text = false;
|
||||
|
||||
lastNamePos = 0;
|
||||
lineNumber = 0;
|
||||
filePos = 0;
|
||||
lastLinePos = 0;
|
||||
messageCount = 0;
|
||||
|
||||
while( Q_memfgets( pMemFile, fileSize, &filePos, buf, 512 ) != NULL )
|
||||
while( Q_memfgets( pfile, fileSize, &filepos, line, sizeof( line )) != NULL )
|
||||
{
|
||||
COM_TrimSpace( trim, buf, sizeof( trim ));
|
||||
char trim[512];
|
||||
COM_TrimSpace( trim, line, sizeof( trim ));
|
||||
|
||||
switch( mode )
|
||||
if( !in_text )
|
||||
{
|
||||
case MSGFILE_NAME:
|
||||
// skip comment lines
|
||||
if( IsComment( trim ))
|
||||
break;
|
||||
|
||||
// Is this a directive "$command"?, if so parse it and break
|
||||
if( ParseDirective( trim ))
|
||||
break;
|
||||
|
||||
if( IsStartOfText( trim ))
|
||||
{
|
||||
mode = MSGFILE_TEXT;
|
||||
pCurrentText = (char*)(pMemFile + filePos);
|
||||
break;
|
||||
}
|
||||
|
||||
if( IsEndOfText( trim ))
|
||||
{
|
||||
Con_Reportf( "%s: unexpected '}' found, line %d\n", __func__, lineNumber );
|
||||
return NULL;
|
||||
}
|
||||
Q_strncpy( currentName, trim, sizeof( currentName ));
|
||||
break;
|
||||
case MSGFILE_TEXT:
|
||||
if( IsEndOfText( trim ))
|
||||
{
|
||||
int length = Q_strlen( currentName );
|
||||
|
||||
// save name on name heap
|
||||
if( lastNamePos + length > 32768 )
|
||||
{
|
||||
Con_Reportf( "%s: error while parsing!\n", __func__ );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Q_strncpy( nameHeap + lastNamePos, currentName, sizeof( nameHeap ) - lastNamePos );
|
||||
|
||||
// terminate text in-place in the memory file
|
||||
// (it's temporary memory that will be deleted)
|
||||
pMemFile[lastLinePos-1] = 0;
|
||||
|
||||
// Save name/text on heap
|
||||
textMessages[messageCount] = gMessageParms;
|
||||
textMessages[messageCount].pName = nameHeap + lastNamePos;
|
||||
lastNamePos += length + 1;
|
||||
textMessages[messageCount].pMessage = pCurrentText;
|
||||
messageCount++;
|
||||
|
||||
// reset parser to search for names
|
||||
mode = MSGFILE_NAME;
|
||||
break;
|
||||
}
|
||||
if( IsStartOfText( trim ))
|
||||
{
|
||||
Con_Reportf( "%s: unexpected '{' found, line %d\n", __func__, lineNumber );
|
||||
return NULL;
|
||||
}
|
||||
break;
|
||||
if( trim[0] == '{' )
|
||||
in_text = true;
|
||||
}
|
||||
|
||||
lineNumber++;
|
||||
lastLinePos = filePos;
|
||||
|
||||
if( messageCount >= MAX_MESSAGES )
|
||||
else if( trim[0] == '}' )
|
||||
{
|
||||
Con_Printf( S_WARN "Too many messages in titles.txt, max is %d\n", MAX_MESSAGES );
|
||||
break;
|
||||
count++;
|
||||
in_text = false;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
Con_Reportf( "%s: parsed %d text messages\n", __func__, messageCount );
|
||||
nameHeapSize = lastNamePos;
|
||||
textHeapSize = 0;
|
||||
client_textmessage_t *CL_TextMessageParse( poolhandle_t mempool, char *pfile, int fileSize, int *numTitles )
|
||||
{
|
||||
int total = TitleCountMessages( pfile, fileSize );
|
||||
|
||||
for( i = 0; i < messageCount; i++ )
|
||||
textHeapSize += Q_strlen( textMessages[i].pMessage ) + 1;
|
||||
messageSize = ( messageCount * sizeof( client_textmessage_t ));
|
||||
|
||||
if(( textHeapSize + nameHeapSize + messageSize ) <= 0 )
|
||||
if( !total )
|
||||
{
|
||||
*numTitles = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// must malloc because we need to be able to clear it after initialization
|
||||
client_textmessage_t *out = Mem_Calloc( mempool, textHeapSize + nameHeapSize + messageSize );
|
||||
size_t bufsize = total * sizeof( client_textmessage_t );
|
||||
client_textmessage_t *out = Mem_Calloc( mempool, bufsize );
|
||||
|
||||
// copy table over
|
||||
memcpy( out, textMessages, messageSize );
|
||||
char line[512], curname[512];
|
||||
int filepos = 0, linenum = 0, count = 0, textsegstart = 0;
|
||||
qboolean in_text = false;
|
||||
|
||||
// copy Name heap
|
||||
pNameHeap = ((char *)out) + messageSize;
|
||||
memcpy( pNameHeap, nameHeap, nameHeapSize );
|
||||
|
||||
// copy text & fixup pointers
|
||||
textHeapSizeRemaining = textHeapSize;
|
||||
pCurrentText = pNameHeap + nameHeapSize;
|
||||
|
||||
for( i = 0; i < messageCount; i++ )
|
||||
while( 1 )
|
||||
{
|
||||
size_t currentTextSize = Q_strlen( out[i].pMessage ) + 1;
|
||||
int curlinestart = filepos;
|
||||
|
||||
out[i].pName = pNameHeap; // adjust name pointer (parallel buffer)
|
||||
Q_strncpy( pCurrentText, out[i].pMessage, textHeapSizeRemaining ); // copy text over
|
||||
out[i].pMessage = pCurrentText;
|
||||
if( Q_memfgets( pfile, fileSize, &filepos, line, sizeof( line )) == NULL )
|
||||
break;
|
||||
|
||||
pNameHeap += Q_strlen( pNameHeap ) + 1;
|
||||
pCurrentText += currentTextSize;
|
||||
textHeapSizeRemaining -= currentTextSize;
|
||||
linenum++;
|
||||
|
||||
char trim[512];
|
||||
COM_TrimSpace( trim, line, sizeof( trim ));
|
||||
|
||||
if( !in_text )
|
||||
{
|
||||
if( COM_StringEmpty( trim ) || ( trim[0] == '/' && trim[1] == '/' ))
|
||||
continue;
|
||||
|
||||
if( TitleParseDirective( trim ))
|
||||
continue;
|
||||
|
||||
if( trim[0] == '{' )
|
||||
{
|
||||
in_text = true;
|
||||
textsegstart = filepos;
|
||||
continue;
|
||||
}
|
||||
|
||||
if( trim[0] == '}' )
|
||||
{
|
||||
Con_Reportf( "%s: unexpected '}' at line %d\n", __func__, linenum );
|
||||
Mem_Free( out );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Q_strncpy( curname, trim, sizeof( curname ));
|
||||
}
|
||||
else
|
||||
{
|
||||
if( trim[0] == '{' )
|
||||
{
|
||||
Con_Reportf( "%s: unexpected '{' at line %d\n", __func__, linenum );
|
||||
Mem_Free( out );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if( trim[0] == '}' )
|
||||
{
|
||||
size_t textlen = Q_max( curlinestart - 1 - textsegstart, 0 );
|
||||
size_t namelen = Q_strlen( curname );
|
||||
size_t nameoffset = bufsize;
|
||||
size_t textoffset = nameoffset + namelen + 1;
|
||||
|
||||
bufsize = textoffset + textlen + 1;
|
||||
out = Mem_Realloc( mempool, out, bufsize );
|
||||
|
||||
char *p = (char *)out;
|
||||
|
||||
memcpy( &p[nameoffset], curname, namelen + 1 );
|
||||
memcpy( &p[textoffset], &pfile[textsegstart], textlen );
|
||||
p[textoffset + textlen] = '\0';
|
||||
|
||||
// store offsets as pointers temporarily
|
||||
out[count] = gMessageParms;
|
||||
out[count].pName = (char *)nameoffset;
|
||||
out[count].pMessage = (char *)textoffset;
|
||||
count++;
|
||||
in_text = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(( pCurrentText - (char *)out ) != ( textHeapSize + nameHeapSize + messageSize ))
|
||||
Con_DPrintf( S_ERROR "%s: overflow text message buffer!\n", __func__ );
|
||||
if( unlikely( count != total ))
|
||||
Con_DPrintf( S_ERROR "%s: expected %d messages, parsed %d\n", __func__, total, count );
|
||||
|
||||
*numTitles = messageCount;
|
||||
// fix up offsets
|
||||
for( int i = 0; i < count; i++ )
|
||||
{
|
||||
out[i].pName = (char *)out + (uintptr_t)out[i].pName;
|
||||
out[i].pMessage = (char *)out + (uintptr_t)out[i].pMessage;
|
||||
}
|
||||
|
||||
Con_Reportf( "%s: parsed %d text messages\n", __func__, count );
|
||||
*numTitles = count;
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user