diff --git a/engine/client/cl_game.c b/engine/client/cl_game.c index a3873a9e..592b705c 100644 --- a/engine/client/cl_game.c +++ b/engine/client/cl_game.c @@ -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 ); } diff --git a/engine/client/client.h b/engine/client/client.h index bf71bdd6..013edf37 100644 --- a/engine/client/client.h +++ b/engine/client/client.h @@ -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; diff --git a/engine/client/titles.c b/engine/client/titles.c index a621a48f..8d1919eb 100644 --- a/engine/client/titles.c +++ b/engine/client/titles.c @@ -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; }