From c19a824a421848707a4fba567c68881ee4e6dd4f Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sun, 1 Mar 2026 03:04:33 +0500 Subject: [PATCH] public: add Q_memfgets, replacement for COM_MemFgets rewritten using crtlib functions. Add a test --- engine/client/cl_pmove.c | 2 +- engine/client/titles.c | 2 +- engine/common/common.c | 54 ------------------------------------- engine/common/common.h | 1 - engine/server/sv_pmove.c | 2 +- public/crtlib.c | 24 +++++++++++++++++ public/crtlib.h | 1 + public/tests/test_strings.c | 51 +++++++++++++++++++++++++++++++++++ 8 files changed, 79 insertions(+), 58 deletions(-) diff --git a/engine/client/cl_pmove.c b/engine/client/cl_pmove.c index 7be252ee..2ece5457 100644 --- a/engine/client/cl_pmove.c +++ b/engine/client/cl_pmove.c @@ -776,7 +776,7 @@ void CL_InitClientMove( void ) clgame.pmove->COM_FileSize = COM_FileSize; clgame.pmove->COM_LoadFile = COM_LoadFile; clgame.pmove->COM_FreeFile = COM_FreeFile; - clgame.pmove->memfgets = COM_MemFgets; + clgame.pmove->memfgets = Q_memfgets; clgame.pmove->PM_PlaySound = pfnPlaySound; clgame.pmove->PM_TraceTexture = PM_CL_TraceTexture; clgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull; diff --git a/engine/client/titles.c b/engine/client/titles.c index 8faaad7f..a621a48f 100644 --- a/engine/client/titles.c +++ b/engine/client/titles.c @@ -227,7 +227,7 @@ client_textmessage_t *CL_TextMessageParse( poolhandle_t mempool, byte *pMemFile, lastLinePos = 0; messageCount = 0; - while( COM_MemFgets( pMemFile, fileSize, &filePos, buf, 512 ) != NULL ) + while( Q_memfgets( pMemFile, fileSize, &filePos, buf, 512 ) != NULL ) { COM_TrimSpace( trim, buf, sizeof( trim )); diff --git a/engine/common/common.c b/engine/common/common.c index 01ae5364..04a4ce05 100644 --- a/engine/common/common.c +++ b/engine/common/common.c @@ -597,7 +597,6 @@ void COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput ) byte *p = pOutput; int i; - for( i = 0; i < nInputLength; i += 2 ) { pIn = &pszInput[i]; @@ -606,59 +605,6 @@ void COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput ) } } -/* -============= -COM_MemFgets - -============= -*/ -char *GAME_EXPORT COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize ) -{ - int i, last, stop; - - if( !pMemFile || !pBuffer || !filePos ) - return NULL; - - if( *filePos >= fileSize ) - return NULL; - - i = *filePos; - last = fileSize; - - // fgets always NULL terminates, so only read bufferSize-1 characters - if( last - *filePos > ( bufferSize - 1 )) - last = *filePos + ( bufferSize - 1); - - stop = 0; - - // stop at the next newline (inclusive) or end of buffer - while( i < last && !stop ) - { - if( pMemFile[i] == '\n' ) - stop = 1; - i++; - } - - // if we actually advanced the pointer, copy it over - if( i != *filePos ) - { - // we read in size bytes - int size = i - *filePos; - - // copy it out - memcpy( pBuffer, pMemFile + *filePos, size ); - - // If the buffer isn't full, terminate (this is always true) - if( size < bufferSize ) pBuffer[size] = 0; - - // update file pointer - *filePos = i; - return pBuffer; - } - - return NULL; -} - /* ============= COM_LoadFileForMe diff --git a/engine/common/common.h b/engine/common/common.h index 6bb9d91e..1f149340 100644 --- a/engine/common/common.h +++ b/engine/common/common.h @@ -628,7 +628,6 @@ qboolean SV_Active( void ); ============================================================== */ -char *COM_MemFgets( byte *pMemFile, int fileSize, int *filePos, char *pBuffer, int bufferSize ); void COM_HexConvert( const char *pszInput, int nInputLength, byte *pOutput ); byte COM_Nibble( char c ); int COM_SaveFile( const char *filename, const void *data, int len ); diff --git a/engine/server/sv_pmove.c b/engine/server/sv_pmove.c index f917431a..4c68db08 100644 --- a/engine/server/sv_pmove.c +++ b/engine/server/sv_pmove.c @@ -484,7 +484,7 @@ void SV_InitClientMove( void ) svgame.pmove->COM_FileSize = COM_FileSize; svgame.pmove->COM_LoadFile = COM_LoadFile; svgame.pmove->COM_FreeFile = COM_FreeFile; - svgame.pmove->memfgets = COM_MemFgets; + svgame.pmove->memfgets = Q_memfgets; svgame.pmove->PM_PlaySound = pfnPlaySound; svgame.pmove->PM_TraceTexture = pfnTraceTexture; svgame.pmove->PM_PlaybackEventFull = pfnPlaybackEventFull; diff --git a/public/crtlib.c b/public/crtlib.c index 1b740611..761959c8 100644 --- a/public/crtlib.c +++ b/public/crtlib.c @@ -22,6 +22,30 @@ GNU General Public License for more details. #include "xash3d_mathlib.h" #include "crtlib.h" +char *GAME_EXPORT Q_memfgets( byte *data, int data_len, int *data_offset, char *dst, int dst_size ) +{ + // sanity check + if( !data || !data_offset || !dst || *data_offset >= data_len ) + return NULL; + + const char *start = (const char *)data + *data_offset; + int remaining = data_len - *data_offset; + + // do not assume the data is null terminated, as we have data_len anyway :) + const char *end = memchr( start, '\n', remaining ); + + if( end ) + remaining = end - start + 1; + + // include null terminator + Q_strncpy( dst, start, Q_min( remaining + 1, dst_size )); + + *data_offset += remaining; + + return dst; +} + + void Q_strnlwr( const char *in, char *out, size_t size_out ) { size_t len, i; diff --git a/public/crtlib.h b/public/crtlib.h index 9feea238..83cf8da8 100644 --- a/public/crtlib.h +++ b/public/crtlib.h @@ -98,6 +98,7 @@ int Q_atoi_hex( int sign, const char *str ); int Q_atoi( const char *str ); float Q_atof( const char *str ); void Q_atov( float *vec, const char *str, size_t siz ); +char *Q_memfgets( byte *data, int data_len, int *data_offset, char *dst, int dst_size ); #define Q_strchr strchr #define Q_strrchr strrchr qboolean Q_stricmpext( const char *pattern, const char *text ); diff --git a/public/tests/test_strings.c b/public/tests/test_strings.c index f3a36472..aacf395e 100644 --- a/public/tests/test_strings.c +++ b/public/tests/test_strings.c @@ -99,6 +99,47 @@ static int Test_TrimSpace( void ) return 0; } +#include + +static int Test_memgets( qboolean include_null_terminator ) +{ + char data[] = + "First line\n" + "Second line\n" + "Third line with long text\n" + "Last line without newline"; + int data_len = sizeof( data ) - ( include_null_terminator ? 1 : 0 ); + string buffer; + int data_offset = 0; + int check_offset = sizeof( "First line\n" ) - 1; + char *p; + + p = Q_memfgets( data, data_len, &data_offset, buffer, sizeof( buffer )); + if( !p || Q_strcmp( buffer, "First line\n" ) || data_offset != check_offset ) + return 1; + + check_offset += sizeof( "Second line\n" ) - 1; + p = Q_memfgets( data, data_len, &data_offset, buffer, sizeof( buffer )); + if( !p || Q_strcmp( buffer, "Second line\n" ) || data_offset != check_offset ) + return 2; + + check_offset += sizeof( "Third line with long text\n" ) - 1; + p = Q_memfgets( data, data_len, &data_offset, buffer, sizeof( buffer )); + if( !p || Q_strcmp( buffer, "Third line with long text\n" ) || data_offset != check_offset ) + return 3; + + check_offset += sizeof( "Last line without newline" ) - ( include_null_terminator ? 1 : 0 ); + p = Q_memfgets( data, data_len, &data_offset, buffer, sizeof( buffer )); + if( !p || Q_strcmp( buffer, "Last line without newline" ) || data_offset != check_offset ) + return 4; + + p = Q_memfgets( data, data_len, &data_offset, buffer, sizeof( buffer )); + if( p ) + return 5; + + return 0; +} + int main( void ) { int ret = Test_Strcpycatcmp(); @@ -121,5 +162,15 @@ int main( void ) if( ret > 0 ) return ret + 48; + ret = Test_memgets( true ); + + if( ret > 0 ) + return ret + 64; + + ret = Test_memgets( false ); + + if( ret > 0 ) + return ret + 64; + return 0; }