1407 lines
30 KiB
C++
1407 lines
30 KiB
C++
![]() |
/*
|
||
|
** Command & Conquer Generals(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 <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
// //
|
||
|
// (c) 2001-2003 Electronic Arts Inc. //
|
||
|
// //
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// Westwood Studios Pacific.
|
||
|
//
|
||
|
// Confidential Information
|
||
|
// Copyright(C) 2001 - All Rights Reserved
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
//
|
||
|
// Project: RTS3
|
||
|
//
|
||
|
// File name: GameText.cpp
|
||
|
//
|
||
|
// Created: 11/07/01
|
||
|
//
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// Includes
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
|
||
|
|
||
|
#include "GameClient/GameText.h"
|
||
|
#include "Common/Language.h"
|
||
|
#include "Common/Registry.h"
|
||
|
#include "GameClient/LanguageFilter.h"
|
||
|
#include "Common/Debug.h"
|
||
|
#include "Common/UnicodeString.h"
|
||
|
#include "Common/AsciiString.h"
|
||
|
#include "Common/GlobalData.h"
|
||
|
#include "Common/File.h"
|
||
|
#include "Common/FileSystem.h"
|
||
|
|
||
|
|
||
|
#ifdef _INTERNAL
|
||
|
// for occasional debugging...
|
||
|
//#pragma optimize("", off)
|
||
|
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
|
||
|
#endif
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// Externals
|
||
|
//----------------------------------------------------------------------------
|
||
|
|
||
|
#if defined(_DEBUG) || defined(_INTERNAL)
|
||
|
Bool g_useStringFile = TRUE;
|
||
|
#endif
|
||
|
|
||
|
|
||
|
//----------------------------------------------------------------------------
|
||
|
// 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
|
||
|
{
|
||
|
AsciiString label;
|
||
|
UnicodeString text;
|
||
|
AsciiString speech;
|
||
|
};
|
||
|
|
||
|
struct StringLookUp
|
||
|
{
|
||
|
AsciiString *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;
|
||
|
UnicodeString 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 UnicodeString fetch( const Char *label, Bool *exists = NULL ); ///< Returns the associated labeled unicode text
|
||
|
virtual UnicodeString fetch( AsciiString label, Bool *exists = NULL ); ///< Returns the associated labeled unicode text
|
||
|
virtual AsciiStringVec& getStringsWithLabelPrefix(AsciiString label);
|
||
|
|
||
|
virtual void initMapStringFile( const AsciiString& filename );
|
||
|
|
||
|
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;
|
||
|
#if defined(_DEBUG) || defined(_INTERNAL)
|
||
|
Bool m_jabberWockie;
|
||
|
Bool m_munkee;
|
||
|
#endif
|
||
|
NoString *m_noStringList;
|
||
|
Int m_useStringFile;
|
||
|
LanguageID m_language;
|
||
|
UnicodeString m_failed;
|
||
|
|
||
|
StringInfo *m_mapStringInfo;
|
||
|
StringLookUp *m_mapStringLUT;
|
||
|
Int m_mapTextCount;
|
||
|
|
||
|
/// m_asciiStringVec will be altered every time that getStringsWithLabelPrefix is called,
|
||
|
/// so don't simply store a pointer to it.
|
||
|
AsciiStringVec m_asciiStringVec;
|
||
|
|
||
|
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( const Char *filename, Int& textCount );
|
||
|
Bool getCSFInfo ( const Char *filename );
|
||
|
Bool parseCSF( const Char *filename );
|
||
|
Bool parseStringFile( const char *filename );
|
||
|
Bool parseMapStringFile( const 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_noStringList(NULL),
|
||
|
#if defined(_DEBUG) || defined(_INTERNAL)
|
||
|
m_jabberWockie(FALSE),
|
||
|
m_munkee(FALSE),
|
||
|
m_useStringFile(g_useStringFile),
|
||
|
#else
|
||
|
m_useStringFile(TRUE),
|
||
|
#endif
|
||
|
m_mapStringInfo(NULL),
|
||
|
m_mapStringLUT(NULL),
|
||
|
m_failed(L"***FATAL*** String Manager failed to initilaize properly")
|
||
|
{
|
||
|
// Added By Sadullah Nader
|
||
|
// Initializations missing and needed
|
||
|
|
||
|
for(Int i=0; i < MAX_UITEXT_LENGTH; i++)
|
||
|
{
|
||
|
m_buffer[i] = 0;
|
||
|
m_buffer2[i] = 0;
|
||
|
m_buffer3[i] = 0;
|
||
|
}
|
||
|
//
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// GameTextManager::~GameTextManager
|
||
|
//============================================================================
|
||
|
|
||
|
GameTextManager::~GameTextManager()
|
||
|
{
|
||
|
deinit();
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// GameTextManager::init
|
||
|
//============================================================================
|
||
|
|
||
|
extern const Char *g_strFile;
|
||
|
extern const Char *g_csfFile;
|
||
|
|
||
|
void GameTextManager::init( void )
|
||
|
{
|
||
|
AsciiString csfFile;
|
||
|
csfFile.format(g_csfFile, GetRegistryLanguage().str());
|
||
|
Int format;
|
||
|
|
||
|
if ( m_initialized )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
m_initialized = TRUE;
|
||
|
|
||
|
m_maxLabelLen = 0;
|
||
|
#if defined(_DEBUG) || defined(_INTERNAL)
|
||
|
if(TheGlobalData)
|
||
|
{
|
||
|
m_jabberWockie = TheGlobalData->m_jabberOn;
|
||
|
m_munkee = TheGlobalData->m_munkeeOn;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if ( m_useStringFile && getStringCount( g_strFile, m_textCount ) )
|
||
|
{
|
||
|
format = STRING_FILE;
|
||
|
}
|
||
|
else if ( getCSFInfo ( csfFile.str() ) )
|
||
|
{
|
||
|
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( g_strFile ) == FALSE )
|
||
|
{
|
||
|
deinit();
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if ( !parseCSF ( csfFile.str() ) )
|
||
|
{
|
||
|
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 );
|
||
|
|
||
|
UnicodeString ourName = fetch("GUI:Command&ConquerGenerals");
|
||
|
extern HWND ApplicationHWnd; ///< our application window handle
|
||
|
if (ApplicationHWnd) {
|
||
|
::SetWindowTextW(ApplicationHWnd, ourName.str());
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// 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 )
|
||
|
{
|
||
|
if( m_mapStringInfo != NULL )
|
||
|
{
|
||
|
delete [] m_mapStringInfo;
|
||
|
m_mapStringInfo = NULL;
|
||
|
}
|
||
|
|
||
|
if( m_mapStringLUT != NULL )
|
||
|
{
|
||
|
delete [] m_mapStringLUT;
|
||
|
m_mapStringLUT = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//============================================================================
|
||
|
// 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 )
|
||
|
{
|
||
|
Int slash = FALSE;
|
||
|
|
||
|
#if defined(_DEBUG) || defined(_INTERNAL)
|
||
|
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;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
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( const char *filename, Int& textCount )
|
||
|
{
|
||
|
Int ok = TRUE;
|
||
|
|
||
|
textCount = 0;
|
||
|
|
||
|
File *file;
|
||
|
file = TheFileSystem->openFile(filename, File::READ | File::TEXT);
|
||
|
DEBUG_LOG(("Looking in %s for string file\n", filename));
|
||
|
|
||
|
if ( file == NULL )
|
||
|
{
|
||
|
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") )
|
||
|
{
|
||
|
textCount++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
textCount += 500;
|
||
|
file->close();
|
||
|
file = NULL;
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// GameTextManager::getCSFInfo
|
||
|
//============================================================================
|
||
|
|
||
|
Bool GameTextManager::getCSFInfo ( const Char *filename )
|
||
|
{
|
||
|
CSFHeader header;
|
||
|
Int ok = FALSE;
|
||
|
File *file = TheFileSystem->openFile(filename, File::READ | File::BINARY);
|
||
|
DEBUG_LOG(("Looking in %s for compiled string file\n", filename));
|
||
|
|
||
|
if ( file != NULL )
|
||
|
{
|
||
|
if ( file->read( &header, sizeof ( header )) == sizeof ( header ) )
|
||
|
{
|
||
|
if ( header.id == CSF_ID )
|
||
|
{
|
||
|
m_textCount = header.num_labels;
|
||
|
|
||
|
if ( header.version >= 2 )
|
||
|
{
|
||
|
m_language = (LanguageID) header.langid;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_language = LANGUAGE_ID_US;
|
||
|
}
|
||
|
|
||
|
ok = TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
file->close();
|
||
|
file = NULL;
|
||
|
}
|
||
|
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// GameTextManager::parseCSF
|
||
|
//============================================================================
|
||
|
|
||
|
Bool GameTextManager::parseCSF( const Char *filename )
|
||
|
{
|
||
|
File *file;
|
||
|
Int id;
|
||
|
Int len;
|
||
|
Int listCount = 0;
|
||
|
Bool ok = FALSE;
|
||
|
CSFHeader header;
|
||
|
|
||
|
file = TheFileSystem->openFile(filename, File::READ | File::BINARY);
|
||
|
|
||
|
if ( file == NULL )
|
||
|
{
|
||
|
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();
|
||
|
file = NULL;
|
||
|
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
|
||
|
//============================================================================
|
||
|
// GameTextManager::parseStringFile
|
||
|
//============================================================================
|
||
|
|
||
|
Bool GameTextManager::parseStringFile( const char *filename )
|
||
|
{
|
||
|
Int listCount = 0;
|
||
|
Int ok = TRUE;
|
||
|
|
||
|
File *file = TheFileSystem->openFile(filename, File::READ | File::TEXT);
|
||
|
|
||
|
if ( file == NULL )
|
||
|
{
|
||
|
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.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();
|
||
|
file = NULL;
|
||
|
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// GameTextManager::initMapStringFile
|
||
|
//============================================================================
|
||
|
|
||
|
void GameTextManager::initMapStringFile( const AsciiString& filename )
|
||
|
{
|
||
|
m_mapTextCount = 0;
|
||
|
getStringCount( filename.str(), m_mapTextCount );
|
||
|
|
||
|
m_mapStringInfo = NEW StringInfo[m_mapTextCount];
|
||
|
|
||
|
parseMapStringFile( filename.str() );
|
||
|
|
||
|
m_mapStringLUT = NEW StringLookUp[m_mapTextCount];
|
||
|
|
||
|
StringLookUp *lut = m_mapStringLUT;
|
||
|
StringInfo *info = m_mapStringInfo;
|
||
|
|
||
|
for ( Int i = 0; i < m_mapTextCount; i++ )
|
||
|
{
|
||
|
lut->info = info;
|
||
|
lut->label = &info->label;
|
||
|
lut++;
|
||
|
info++;
|
||
|
}
|
||
|
|
||
|
qsort( m_mapStringLUT, m_mapTextCount, sizeof(StringLookUp), compareLUT );
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// GameTextManager::parseMapStringFile
|
||
|
//============================================================================
|
||
|
|
||
|
Bool GameTextManager::parseMapStringFile( const char *filename )
|
||
|
{
|
||
|
Int listCount = 0;
|
||
|
Int ok = TRUE;
|
||
|
|
||
|
File *file;
|
||
|
|
||
|
file = TheFileSystem->openFile(filename, File::READ | File::TEXT);
|
||
|
if ( file == NULL )
|
||
|
{
|
||
|
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_mapStringInfo[i].label.str(), m_buffer ))
|
||
|
{
|
||
|
DEBUG_ASSERTCRASH ( FALSE, ("String label '%s' multiply defined!", m_buffer ));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
m_mapStringInfo[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 allowed
|
||
|
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 );
|
||
|
|
||
|
UnicodeString text = UnicodeString(m_tbuffer);
|
||
|
if (TheLanguageFilter)
|
||
|
TheLanguageFilter->filterLine(text);
|
||
|
|
||
|
m_mapStringInfo[listCount].text = text;
|
||
|
m_mapStringInfo[listCount].speech = m_buffer3;
|
||
|
readString = TRUE;
|
||
|
}
|
||
|
}
|
||
|
else if ( !stricmp ( m_buffer, "END" ))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
listCount++;
|
||
|
}
|
||
|
|
||
|
quit:
|
||
|
|
||
|
file->close();
|
||
|
file = NULL;
|
||
|
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// *GameTextManager::fetch
|
||
|
//============================================================================
|
||
|
|
||
|
UnicodeString GameTextManager::fetch( const Char *label, Bool *exists )
|
||
|
{
|
||
|
DEBUG_ASSERTCRASH ( m_initialized, ("String Manager has not been m_initialized") );
|
||
|
|
||
|
if( m_stringInfo == NULL )
|
||
|
{
|
||
|
if( exists )
|
||
|
*exists = FALSE;
|
||
|
return m_failed;
|
||
|
}
|
||
|
|
||
|
StringLookUp *lookUp;
|
||
|
StringLookUp key;
|
||
|
AsciiString lb;
|
||
|
lb = label;
|
||
|
key.info = NULL;
|
||
|
key.label = &lb;
|
||
|
|
||
|
lookUp = (StringLookUp *) bsearch( &key, (void*) m_stringLUT, m_textCount, sizeof(StringLookUp), compareLUT );
|
||
|
|
||
|
if ( lookUp == NULL && m_mapStringLUT && m_mapTextCount )
|
||
|
{
|
||
|
lookUp = (StringLookUp *) bsearch( &key, (void*) m_mapStringLUT, m_mapTextCount, sizeof(StringLookUp), compareLUT );
|
||
|
}
|
||
|
|
||
|
if( lookUp == NULL )
|
||
|
{
|
||
|
|
||
|
// string not found
|
||
|
if( exists )
|
||
|
*exists = FALSE;
|
||
|
|
||
|
// See if we already have the missing string
|
||
|
UnicodeString missingString;
|
||
|
missingString.format(L"MISSING: '%hs'", label);
|
||
|
|
||
|
NoString *noString = m_noStringList;
|
||
|
|
||
|
while ( noString )
|
||
|
{
|
||
|
if (noString->text == missingString)
|
||
|
return missingString;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
if( exists )
|
||
|
*exists = TRUE;
|
||
|
return lookUp->info->text;
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// *GameTextManager::fetch
|
||
|
//============================================================================
|
||
|
|
||
|
UnicodeString GameTextManager::fetch( AsciiString label, Bool *exists )
|
||
|
{
|
||
|
return fetch(label.str(), exists);
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// GameTextManager::getStringsWithLabelPrefix
|
||
|
//============================================================================
|
||
|
|
||
|
AsciiStringVec& GameTextManager::getStringsWithLabelPrefix(AsciiString label)
|
||
|
{
|
||
|
m_asciiStringVec.clear();
|
||
|
if (m_stringLUT) {
|
||
|
for (int i = 0; i < m_textCount; ++i) {
|
||
|
if (strstr(m_stringLUT[i].label->str(), label.str()) == m_stringLUT[i].label->str()) {
|
||
|
m_asciiStringVec.push_back(*m_stringLUT[i].label);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (m_mapStringLUT) {
|
||
|
for (int i = 0; i < m_mapTextCount; ++i) {
|
||
|
if (strstr(m_mapStringLUT[i].label->str(), label.str()) == m_mapStringLUT[i].label->str()) {
|
||
|
m_asciiStringVec.push_back(*m_mapStringLUT[i].label);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return m_asciiStringVec;
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// 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->str(), lut2->label->str());
|
||
|
}
|