/* ** Command & Conquer Generals Zero Hour(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program 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. ** ** This program 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 this program. If not, see . */ //---------------------------------------------------------------------------- // // Westwood Studios Pacific. // // Confidential Information // Copyright(C) 2001 - All Rights Reserved // //---------------------------------------------------------------------------- // // Project: RTS3 // // File name: GameText.cpp // // Created: 11/07/01 // //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // Includes //---------------------------------------------------------------------------- #include #include #include #include #include #include #include "GameText.h" #define DEBUG_LOG(x) {} #define DEBUG_ASSERTCRASH(x, y) {} //#include //#include //#include //#include //#include #include "WSYS_File.h" #include "WSYS_RAMFile.h" //---------------------------------------------------------------------------- // Externals //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // Defines //---------------------------------------------------------------------------- #define CSF_ID ( ('C'<<24) | ('S'<<16) | ('F'<<8) | (' ') ) #define CSF_LABEL ( ('L'<<24) | ('B'<<16) | ('L'<<8) | (' ') ) #define CSF_STRING ( ('S'<<24) | ('T'<<16) | ('R'<<8) | (' ') ) #define CSF_STRINGWITHWAVE ( ('S'<<24) | ('T'<<16) | ('R'<<8) | ('W') ) #define CSF_VERSION 3 #define STRING_FILE 0 #define CSF_FILE 1 #define MAX_UITEXT_LENGTH (10*1024) //---------------------------------------------------------------------------- // Private Types //---------------------------------------------------------------------------- //=============================== // StringInfo //=============================== struct StringInfo { std::string label; std::wstring text; std::string speech; }; struct StringLookUp { std::string *label; StringInfo *info; }; //=============================== // CSFHeader //=============================== struct CSFHeader { Int id; Int version; Int num_labels; Int num_strings; Int skip; Int langid; }; //=============================== // struct NoString //=============================== struct NoString { struct NoString *next; std::wstring text; }; //=============================== // GameTextManager //=============================== class GameTextManager : public GameTextInterface { public: GameTextManager(); virtual ~GameTextManager(); virtual void init( void ); ///< Initlaizes the text system virtual void deinit( void ); ///< De-initlaizes the text system virtual void update( void ) {}; ///< update text manager virtual void reset( void ); ///< Resets the text system virtual const wchar_t * fetch( const Char *label ); ///< Returns the associated labeled unicode text protected: Int m_textCount; Int m_maxLabelLen; Char m_buffer[MAX_UITEXT_LENGTH]; Char m_buffer2[MAX_UITEXT_LENGTH]; Char m_buffer3[MAX_UITEXT_LENGTH]; WideChar m_tbuffer[MAX_UITEXT_LENGTH*2]; StringInfo *m_stringInfo; StringLookUp *m_stringLUT; Bool m_initialized; Bool m_jabberWockie; Bool m_munkee; NoString *m_noStringList; Int m_useStringFile; std::wstring m_failed; void stripSpaces ( WideChar *string ); void removeLeadingAndTrailing ( Char *m_buffer ); void readToEndOfQuote( File *file, Char *in, Char *out, Char *wavefile, Int maxBufLen ); void reverseWord ( Char *file, Char *lp ); void translateCopy( WideChar *outbuf, Char *inbuf ); Bool getStringCount( Char *filename); Bool getCSFInfo ( Char *filename ); Bool parseCSF( Char *filename ); Bool parseStringFile( char *filename ); Bool readLine( char *buffer, Int max, File *file ); Char readChar( File *file ); }; static int _cdecl compareLUT ( const void *, const void*); //---------------------------------------------------------------------------- // Private Data //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // Public Data //---------------------------------------------------------------------------- GameTextInterface *TheGameText = NULL; //---------------------------------------------------------------------------- // Private Prototypes //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // Private Functions //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // Public Functions //---------------------------------------------------------------------------- //============================================================================ // CreateGameTextInterface //============================================================================ GameTextInterface* CreateGameTextInterface( void ) { return new GameTextManager; } //============================================================================ // GameTextManager::GameTextManager //============================================================================ GameTextManager::GameTextManager() : m_textCount(0), m_maxLabelLen(0), m_stringInfo(NULL), m_stringLUT(NULL), m_initialized(FALSE), m_jabberWockie(FALSE), m_munkee(FALSE), m_noStringList(NULL), m_useStringFile(TRUE), m_failed(L"***FATAL*** String Manager failed to initilaized properly") { } //============================================================================ // GameTextManager::~GameTextManager //============================================================================ GameTextManager::~GameTextManager() { deinit(); } //============================================================================ // GameTextManager::init //============================================================================ extern char szArgvPath[]; void GameTextManager::init( void ) { Char *strFile = "autorun.str"; Char *csfFile = "autorun.csf"; Int format; Char realStrFile[_MAX_PATH]; Char realCsfFile[_MAX_PATH]; strncpy(realStrFile, szArgvPath, _MAX_PATH); strncpy(realCsfFile, szArgvPath, _MAX_PATH); strncat(realStrFile, strFile, _MAX_PATH - strlen(realStrFile)); strncat(realCsfFile, csfFile, _MAX_PATH - strlen(realCsfFile)); if ( m_initialized ) { return; } m_initialized = TRUE; m_maxLabelLen = 0; m_jabberWockie = FALSE; m_munkee = FALSE; if ( m_useStringFile && getStringCount( realStrFile) ) { format = STRING_FILE; } else if ( getCSFInfo ( realCsfFile ) ) { format = CSF_FILE; } else { return; } if( (m_textCount == 0) ) { return; } //Allocate StringInfo Array m_stringInfo = new StringInfo[m_textCount]; if( m_stringInfo == NULL ) { deinit(); return; } if ( format == STRING_FILE ) { if( parseStringFile( realStrFile ) == FALSE ) { deinit(); return; } } else { if ( !parseCSF ( realCsfFile ) ) { deinit(); return; } } m_stringLUT = new StringLookUp[m_textCount]; StringLookUp *lut = m_stringLUT; StringInfo *info = m_stringInfo; for ( Int i = 0; i < m_textCount; i++ ) { lut->info = info; lut->label = &info->label; lut++; info++; } qsort( m_stringLUT, m_textCount, sizeof(StringLookUp), compareLUT ); } //============================================================================ // GameTextManager::deinit //============================================================================ void GameTextManager::deinit( void ) { if( m_stringInfo != NULL ) { delete [] m_stringInfo; m_stringInfo = NULL; } if( m_stringLUT != NULL ) { delete [] m_stringLUT; m_stringLUT = NULL; } m_textCount = 0; NoString *noString = m_noStringList; DEBUG_LOG(("\n*** Missing strings ***\n")); while ( noString ) { DEBUG_LOG(("*** %ls ***\n", noString->text.str())); NoString *next = noString->next; delete noString; noString = next; } DEBUG_LOG(("*** End missing strings ***\n\n")); m_noStringList = NULL; m_initialized = FALSE; } //============================================================================ // GameTextManager::reset //============================================================================ void GameTextManager::reset( void ) { } //============================================================================ // GameTextManager::stripSpaces //============================================================================ void GameTextManager::stripSpaces ( WideChar *string ) { WideChar *str, *ptr; WideChar ch, last = 0; Int skipall = TRUE; str = ptr = string; while ( (ch = *ptr++) != 0 ) { if ( ch == ' ' ) { if ( last == ' ' || skipall ) { continue; } } if ( ch == '\n' || ch == '\t' ) { // remove last space if ( last == ' ' ) { str--; } skipall = TRUE; // skip all spaces last = *str++ = ch; continue; } last = *str++ = ch; skipall = FALSE; } if ( last == ' ' ) { str--; } *str = 0; } //============================================================================ // GameTextManager::removeLeadingAndTrailing //============================================================================ void GameTextManager::removeLeadingAndTrailing ( Char *buffer ) { Char *first, *ptr; Char ch; ptr = first = buffer; while ( (ch = *first) != 0 && iswspace ( ch )) { first++; } while ( (*ptr++ = *first++) != 0 ); ptr -= 2;; while ( (ptr > buffer) && (ch = *ptr) != 0 && iswspace ( ch ) ) { ptr--; } ptr++; *ptr = 0; } //============================================================================ // GameTextManager::readToEndOfQuote //============================================================================ void GameTextManager::readToEndOfQuote( File *file, Char *in, Char *out, Char *wavefile, Int maxBufLen ) { Int slash = FALSE; Int state = 0; Int line_start = FALSE; Char ch; Int ccount = 0; Int len = 0; Int done = FALSE; while ( maxBufLen ) { // get next Char if ( in ) { if ( (ch = *in++) == 0 ) { in = NULL; // have exhausted the input m_buffer ch = readChar ( file ); } } else { ch = readChar ( file ); } if ( ch == EOF ) { return ; } if ( ch == '\n' ) { line_start = TRUE; slash = FALSE; ccount = 0; ch = ' '; } else if ( ch == '\\' && !slash) { slash = TRUE; } else if ( ch == '\\' && slash) { slash = FALSE; } else if ( ch == '"' && !slash ) { break; // done } else { slash = FALSE; } if ( iswspace ( ch )) { ch = ' '; } *out++ = ch; maxBufLen--; } *out = 0; while ( !done ) { // get next Char if ( in ) { if ( (ch = *in++) == 0 ) { in = NULL; // have exhausted the input m_buffer ch = readChar ( file ); } } else { ch = readChar ( file ); } if ( ch == '\n' || ch == EOF ) { break; } switch ( state ) { case 0: if ( iswspace ( ch ) || ch == '=' ) { break; } state = 1; case 1: if ( ( ch >= 'a' && ch <= 'z') || ( ch >= 'A' && ch <='Z') || (ch >= '0' && ch <= '9') || ch == '_' ) { *wavefile++ = ch; len++; break; } state = 2; case 2: break; } } *wavefile = 0; if ( len ) { if ( ( ch = *(wavefile-1)) >= '0' && ch <= '9' ) { *wavefile++ = 'e'; *wavefile = 0; } } } //============================================================================ // GameTextManager::reverseWord //============================================================================ void GameTextManager::reverseWord ( Char *file, Char *lp ) { Int first = TRUE; Char f, l; Int ok = TRUE ; while ( ok ) { if ( file >= lp ) { return; } f = *file; l = *lp; if ( first ) { if ( f >= 'A' && f <= 'Z' ) { if ( l >= 'a' && l <= 'z' ) { f = (f - 'A') + 'a'; l = (l - 'a') + 'A'; } } first = FALSE; } *lp-- = f; *file++ = l; } } //============================================================================ // GameTextManager::translateCopy //============================================================================ void GameTextManager::translateCopy( WideChar *outbuf, Char *inbuf ) { Bool slash = FALSE; if ( m_jabberWockie ) { static Char buffer[MAX_UITEXT_LENGTH*2]; Char *firstLetter = NULL, *lastLetter; Char *b = buffer; Int formatWord = FALSE; Char ch; while ( (ch = *inbuf++) != 0 ) { if ( ! (( ch >= 'a' && ch <= 'z') || ( ch >= 'A' && ch <= 'Z' ))) { if ( firstLetter ) { if ( !formatWord ) { lastLetter = b-1; reverseWord ( firstLetter, lastLetter ); } firstLetter = NULL; formatWord = FALSE; } *b++ = ch; if ( ch == '\\' ) { *b++ = *inbuf++; } if ( ch == '%' ) { while ( (ch = *inbuf++) != 0 && !( (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))) { *b++ = ch; } *b++ = ch; } } else { if ( !firstLetter ) { firstLetter = b; } *b++ = ch; } } if ( firstLetter ) { lastLetter = b-1; reverseWord ( firstLetter, lastLetter ); } *b++ = 0; inbuf = buffer; } else if( m_munkee ) { wcscpy(outbuf, L"Munkee"); return; } while( *inbuf != '\0' ) { if( slash == TRUE ) { slash = FALSE; switch( *inbuf ) { // in case end of string is reached // should never happen!!! case '\0': return; case '\\': *outbuf++ = '\\'; break; case '\'': *outbuf++ = '\''; break; case '\"': *outbuf++ = '\"'; break; case '\?': *outbuf++ = '\?'; break; case 't': *outbuf++ = '\t'; break; case 'n': *outbuf++ = '\n'; break; default: *outbuf++ = *inbuf & 0x00FF; break; } } else if( *inbuf != '\\' ) { *outbuf++ = *inbuf & 0x00FF; } else slash = TRUE; inbuf++; } *outbuf= 0; } //============================================================================ // GameTextManager::getStringCount //============================================================================ Bool GameTextManager::getStringCount( char *filename ) { Int ok = TRUE; m_textCount = 0; RAMFile file; if ( !file.open( filename, File::READ | File::TEXT )) { return FALSE; } while(ok) { if( !readLine( m_buffer, sizeof( m_buffer) -1, &file ) ) break; removeLeadingAndTrailing ( m_buffer ); if( m_buffer[0] == '"' ) { Int len = strlen(m_buffer); m_buffer[ len ] = '\n'; m_buffer[ len+1] = 0; readToEndOfQuote( &file, &m_buffer[1], m_buffer2, m_buffer3, MAX_UITEXT_LENGTH ); } else if( !stricmp( m_buffer, "END") ) { m_textCount++; } } m_textCount += 500; file.close(); return TRUE; } //============================================================================ // GameTextManager::getCSFInfo //============================================================================ Bool GameTextManager::getCSFInfo ( Char *filename ) { CSFHeader header; Int ok = FALSE; RAMFile file; if ( file.open( filename, File::READ | File::BINARY ) ) { if ( file.read( &header, sizeof ( header )) == sizeof ( header ) ) { if ( header.id == CSF_ID ) { m_textCount = header.num_labels; ok = TRUE; } } file.close(); } return ok; } //============================================================================ // GameTextManager::parseCSF //============================================================================ Bool GameTextManager::parseCSF( Char *filename ) { RAMFile file; Int id; Int len; Int listCount = 0; Bool ok = FALSE; CSFHeader header; if ( !file.open( filename, File::READ | File::BINARY ) ) { return FALSE; } if ( file.read ( &header, sizeof ( CSFHeader)) != sizeof ( CSFHeader) ) { return FALSE; } while( file.read ( &id, sizeof (id)) == sizeof ( id) ) { Int num; Int num_strings; if ( id != CSF_LABEL ) { goto quit; } file.read ( &num_strings, sizeof ( Int )); file.read ( &len, sizeof ( Int ) ); if ( len ) { file.read ( m_buffer, len ); } m_buffer[len] = 0; m_stringInfo[listCount].label = m_buffer; if ( len > m_maxLabelLen ) { m_maxLabelLen = len; } num = 0; while ( num < num_strings ) { file.read ( &id, sizeof ( Int ) ); if ( id != CSF_STRING && id != CSF_STRINGWITHWAVE ) { goto quit; } file.read ( &len, sizeof ( Int ) ); if ( len ) { file.read ( m_tbuffer, len*sizeof(WideChar) ); } if ( num == 0 ) { // only use the first string found m_tbuffer[len] = 0; { WideChar *ptr; ptr = m_tbuffer; while ( *ptr ) { *ptr = ~*ptr; ptr++; } } stripSpaces ( m_tbuffer ); m_stringInfo[listCount].text = m_tbuffer; } if ( id == CSF_STRINGWITHWAVE ) { file.read ( &len, sizeof ( Int ) ); if ( len ) { file.read ( m_buffer, len ); } m_buffer[len] = 0; if ( num == 0 && len ) { // only use the first string found m_stringInfo[listCount].speech = m_buffer; } } num++; } listCount++; } ok = TRUE; quit: file.close(); return ok; } //============================================================================ // GameTextManager::parseStringFile //============================================================================ Bool GameTextManager::parseStringFile( char *filename ) { Int listCount = 0; Int ok = TRUE; RAMFile file; if ( !file.open( filename, File::READ | File::TEXT ) ) { return FALSE; } while( ok ) { Int len; if( !readLine( m_buffer, MAX_UITEXT_LENGTH, &file )) { break; } removeLeadingAndTrailing ( m_buffer ); if( ( *(unsigned short *)m_buffer == 0x2F2F) || !m_buffer[0]) // 0x2F2F is Hex for // continue; // make sure label is unique for ( Int i = 0; i < listCount; i++ ) { if ( !stricmp ( m_stringInfo[i].label.c_str(), m_buffer )) { DEBUG_ASSERTCRASH ( FALSE, ("String label '%s' multiply defined!", m_buffer )); } } m_stringInfo[listCount].label = m_buffer; len = strlen ( m_buffer ); if ( len > m_maxLabelLen ) { m_maxLabelLen = len; } Bool readString = FALSE; while( ok ) { if (!readLine ( m_buffer, sizeof(m_buffer)-1, &file )) { DEBUG_ASSERTCRASH (FALSE, ("Unexpected end of string file")); ok = FALSE; goto quit; } removeLeadingAndTrailing ( m_buffer ); if( m_buffer[0] == '"' ) { len = strlen(m_buffer); m_buffer[ len ] = '\n'; m_buffer[ len+1] = 0; readToEndOfQuote( &file, &m_buffer[1], m_buffer2, m_buffer3, MAX_UITEXT_LENGTH ); if ( readString ) { // only one string per label allows DEBUG_ASSERTCRASH ( FALSE, ("String label '%s' has more than one string defined!", m_stringInfo[listCount].label.str())); } else { // Copy string into new home translateCopy( m_tbuffer, m_buffer2 ); stripSpaces ( m_tbuffer ); m_stringInfo[listCount].text = m_tbuffer ; m_stringInfo[listCount].speech = m_buffer3; readString = TRUE; } } else if ( !stricmp ( m_buffer, "END" )) { break; } } listCount++; } quit: file.close(); return ok; } //============================================================================ // *GameTextManager::fetch //============================================================================ const wchar_t * GameTextManager::fetch( const Char *label ) { DEBUG_ASSERTCRASH ( m_initialized, ("String Manager has not been m_initialized") ); if( m_stringInfo == NULL ) { return m_failed.c_str(); } StringLookUp *lookUp; StringLookUp key; std::string lb; lb = label; key.info = NULL; key.label = &lb; lookUp = (StringLookUp *) bsearch( &key, (void*) m_stringLUT, m_textCount, sizeof(StringLookUp), compareLUT ); if( lookUp == NULL ) { // See if we already have the missing string wchar_t tmp[256]; _snwprintf(tmp, 256, L"MISSING: '%hs'", label); tmp[255] = 0; std::wstring missingString = tmp; NoString *noString = m_noStringList; while ( noString ) { if (noString->text == missingString) return missingString.c_str(); noString = noString->next; } //DEBUG_LOG(("*** MISSING:'%s' ***\n", label)); // Remember file could have been altered at this point. noString = new NoString; noString->text = missingString; noString->next = m_noStringList; m_noStringList = noString; return noString->text.c_str(); } return lookUp->info->text.c_str(); } //============================================================================ // GameTextManager::readLine //============================================================================ Bool GameTextManager::readLine( char *buffer, Int max, File *file ) { Int ok = FALSE; while ( max && file->read( buffer, 1 ) == 1 ) { ok = TRUE; if ( *buffer == '\n' ) { break; } buffer++; max--; } *buffer = 0; return ok; } //============================================================================ // GameTextManager::readChar //============================================================================ Char GameTextManager::readChar( File *file ) { Char ch; if ( file->read( &ch, 1 ) == 1 ) { return ch; } return 0; } //============================================================================ // GameTextManager::compareLUT //============================================================================ static int __cdecl compareLUT ( const void *i1, const void*i2) { StringLookUp *lut1 = (StringLookUp*) i1; StringLookUp *lut2 = (StringLookUp*) i2; return stricmp( lut1->label->c_str(), lut2->label->c_str()); }