engine: client: add simple test for titles.txt parser

This commit is contained in:
Alibek Omarov
2026-02-28 01:49:25 +05:00
parent 7ce830ea33
commit 56255f6de0
4 changed files with 116 additions and 21 deletions
+1 -1
View File
@@ -583,7 +583,7 @@ static void CL_InitTitles( const char *filename )
pMemFile = FS_LoadFile( filename, &fileSize, false );
if( !pMemFile ) return;
CL_TextMessageParse( pMemFile, fileSize );
clgame.titles = CL_TextMessageParse( clgame.mempool, pMemFile, fileSize, &clgame.numTitles );
Mem_Free( pMemFile );
}
+1 -1
View File
@@ -851,7 +851,7 @@ void CL_ClearWorld( void );
void CL_DrawCenterPrint( void );
void CL_ClearSpriteTextures( void );
void CL_CenterPrint( const char *text, float y );
void CL_TextMessageParse( byte *pMemFile, int fileSize );
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 );
+109 -18
View File
@@ -15,6 +15,7 @@ GNU General Public License for more details.
#include "common.h"
#include "client.h"
#include "tests.h"
#define MAX_MESSAGES 2048
@@ -208,7 +209,7 @@ static int ParseDirective( const char *pText )
return 0;
}
void CL_TextMessageParse( byte *pMemFile, int fileSize )
client_textmessage_t *CL_TextMessageParse( poolhandle_t mempool, byte *pMemFile, int fileSize, int *numTitles )
{
char buf[512], trim[512], currentName[512];
char *pCurrentText = NULL, *pNameHeap;
@@ -251,7 +252,7 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize )
if( IsEndOfText( trim ))
{
Con_Reportf( "%s: unexpected '}' found, line %d\n", __func__, lineNumber );
return;
return NULL;
}
Q_strncpy( currentName, trim, sizeof( currentName ));
break;
@@ -264,7 +265,7 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize )
if( lastNamePos + length > 32768 )
{
Con_Reportf( "%s: error while parsing!\n", __func__ );
return;
return NULL;
}
Q_strncpy( nameHeap + lastNamePos, currentName, sizeof( nameHeap ) - lastNamePos );
@@ -287,7 +288,7 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize )
if( IsStartOfText( trim ))
{
Con_Reportf( "%s: unexpected '{' found, line %d\n", __func__, lineNumber );
return;
return NULL;
}
break;
}
@@ -312,22 +313,19 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize )
if(( textHeapSize + nameHeapSize + messageSize ) <= 0 )
{
clgame.titles = NULL;
clgame.numTitles = 0;
return;
*numTitles = 0;
return NULL;
}
// must malloc because we need to be able to clear it after initialization
clgame.titles = (client_textmessage_t *)Mem_Calloc( cls.mempool, textHeapSize + nameHeapSize + messageSize );
client_textmessage_t *out = Mem_Calloc( mempool, textHeapSize + nameHeapSize + messageSize );
// copy table over
memcpy( clgame.titles, textMessages, messageSize );
memcpy( out, textMessages, messageSize );
// copy Name heap
pNameHeap = ((char *)clgame.titles) + messageSize;
pNameHeap = ((char *)out) + messageSize;
memcpy( pNameHeap, nameHeap, nameHeapSize );
//nameOffset = pNameHeap - clgame.titles[0].pName; //undefined on amd64
// copy text & fixup pointers
textHeapSizeRemaining = textHeapSize;
@@ -335,19 +333,112 @@ void CL_TextMessageParse( byte *pMemFile, int fileSize )
for( i = 0; i < messageCount; i++ )
{
size_t currentTextSize = Q_strlen( clgame.titles[i].pMessage ) + 1;
size_t currentTextSize = Q_strlen( out[i].pMessage ) + 1;
clgame.titles[i].pName = pNameHeap; // adjust name pointer (parallel buffer)
Q_strncpy( pCurrentText, clgame.titles[i].pMessage, textHeapSizeRemaining ); // copy text over
clgame.titles[i].pMessage = pCurrentText;
out[i].pName = pNameHeap; // adjust name pointer (parallel buffer)
Q_strncpy( pCurrentText, out[i].pMessage, textHeapSizeRemaining ); // copy text over
out[i].pMessage = pCurrentText;
pNameHeap += Q_strlen( pNameHeap ) + 1;
pCurrentText += currentTextSize;
textHeapSizeRemaining -= currentTextSize;
}
if(( pCurrentText - (char *)clgame.titles ) != ( textHeapSize + nameHeapSize + messageSize ))
if(( pCurrentText - (char *)out ) != ( textHeapSize + nameHeapSize + messageSize ))
Con_DPrintf( S_ERROR "%s: overflow text message buffer!\n", __func__ );
clgame.numTitles = messageCount;
*numTitles = messageCount;
return out;
}
#if XASH_ENGINE_TESTS
void Test_RunTitles( void )
{
poolhandle_t mempool = Mem_AllocPool( __func__ );
client_textmessage_t *tmessages;
client_textmessage_t *null;
int num_titles = 0, num_null_titles = 0;
char titles[] =
"// this is a comment\n"
"$effect 2\n"
"$color 1 2 3\n"
"$color2 4 5 6\n"
"$position 7 8\n"
"$fadein 0.1\n"
"$fadeout 0.5\n"
"$holdtime 321\n"
"$fxtime 123\n"
"TITLE\n"
"{\n"
"Hello to anybody reading test data\n"
"Hope you have a good time\n"
"}\n"
"\n"
"TITLE2\n"
"{\n"
"Still reading that nonsense huh?\n"
"}\n"
"// let's override some things\n"
"$cats are\n"
"$cute\n"
"$color 10 10 10\n"
"$position -1 -1\n"
"UwU\n"
"{\n"
"OwO\n"
"}\n"
"Technically titles can have spaces\n"
"{\n"
"Oh yeah!\n"
"}\n";
char broken_titles[] = "}\n";
char broken_titles2[] = "{\n{\n";
null = CL_TextMessageParse( mempool, broken_titles, Q_strlen( broken_titles ), &num_null_titles );
TASSERT_EQi( num_null_titles, 0 );
TASSERT_EQp( null, NULL );
null = CL_TextMessageParse( mempool, broken_titles2, Q_strlen( broken_titles2 ), &num_null_titles );
TASSERT_EQi( num_null_titles, 0 );
TASSERT_EQp( null, NULL );
tmessages = CL_TextMessageParse( mempool, titles, Q_strlen( titles ), &num_titles );
TASSERT_EQi( num_titles, 4 );
TASSERT_NEQp( tmessages, NULL );
for( int i = 0; i < 4; i++ )
{
TASSERT_EQi( tmessages[i].effect, 2 );
TASSERT_EQi( tmessages[i].r1, i >= 2 ? 10 : 1 );
TASSERT_EQi( tmessages[i].g1, i >= 2 ? 10 : 2 );
TASSERT_EQi( tmessages[i].b1, i >= 2 ? 10 : 3 );
TASSERT_EQi( tmessages[i].a1, 0 );
TASSERT_EQi( tmessages[i].r2, 4 );
TASSERT_EQi( tmessages[i].g2, 5 );
TASSERT_EQi( tmessages[i].b2, 6 );
TASSERT_EQi( tmessages[i].a2, 0 );
TASSERT_EQi( tmessages[i].x, i >= 2 ? -1 : 7 );
TASSERT_EQi( tmessages[i].y, i >= 2 ? -1 : 8 );
TASSERT_EQi( tmessages[i].fadein, 0.1f );
TASSERT_EQi( tmessages[i].fadeout, 0.5f );
TASSERT_EQi( tmessages[i].holdtime, 321.f );
TASSERT_EQi( tmessages[i].fxtime, 123.f );
}
TASSERT_STR( tmessages[0].pName, "TITLE" );
TASSERT_STR( tmessages[1].pName, "TITLE2" );
TASSERT_STR( tmessages[2].pName, "UwU" );
TASSERT_STR( tmessages[3].pName, "Technically titles can have spaces" );
TASSERT_STR( tmessages[0].pMessage, "Hello to anybody reading test data\nHope you have a good time" );
TASSERT_STR( tmessages[1].pMessage, "Still reading that nonsense huh?" );
TASSERT_STR( tmessages[2].pMessage, "OwO" );
TASSERT_STR( tmessages[3].pMessage, "Oh yeah!" );
Mem_Free( tmessages );
Mem_FreePool( &mempool );
}
#endif // XASH_ENGINE_TESTS
+5 -1
View File
@@ -29,6 +29,8 @@ extern struct tests_stats_s tests_stats;
_TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%d\" != \"%d\"\n", __FILE__, __LINE__, (int)val1, (int)val2 ))
#define TASSERT_EQp( val1, val2 ) \
_TASSERT( ( val1 ) != ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%p\" != \"%p\"\n", __FILE__, __LINE__, val1, val2 ))
#define TASSERT_NEQp( val1, val2 ) \
_TASSERT( ( val1 ) == ( val2 ), Msg( S_ERROR "assert failed at %s:%i, \"%p\" == \"%p\"\n", __FILE__, __LINE__, val1, val2 ))
#define TASSERT_STR( str1, str2 ) \
_TASSERT( Q_strcmp(( str1 ), ( str2 )), Msg( S_ERROR "assert failed at %s:%i, \"%s\" != \"%s\"\n", __FILE__, __LINE__, ( str1 ), ( str2 )))
@@ -44,6 +46,7 @@ void Test_RunGamma( void );
void Test_RunDelta( void );
void Test_RunBuffer( void );
void Test_RunMunge( void );
void Test_RunTitles( void );
#define TEST_LIST_0 \
Test_RunLibCommon(); \
@@ -63,7 +66,8 @@ void Test_RunMunge( void );
Test_RunImagelib();
#define TEST_LIST_1_CLIENT \
Test_RunVOX();
Test_RunVOX(); \
Test_RunTitles();
#endif