mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2024-12-13 22:11:15 +00:00
721 lines
16 KiB
C++
721 lines
16 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 BFG Edition GPL Source Code
|
|
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
|
|
|
Doom 3 BFG Edition Source Code 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.
|
|
|
|
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
#pragma hdrstop
|
|
#include "precompiled.h"
|
|
|
|
// This is the default language dict that the entire system uses, but you can instantiate your own idLangDict classes to manipulate a language dictionary in a tool
|
|
idLangDict idLocalization::languageDict;
|
|
|
|
idCVar lang_maskLocalizedStrings( "lang_maskLocalizedStrings", "0", CVAR_BOOL, "Masks all localized strings to help debugging. When set will replace strings with an equal length of W's and ending in an X. Note: The masking occurs at string table load time." );
|
|
|
|
/*
|
|
========================
|
|
idLocalization::ClearDictionary
|
|
========================
|
|
*/
|
|
void idLocalization::ClearDictionary()
|
|
{
|
|
languageDict.Clear();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLocalization::LoadDictionary
|
|
========================
|
|
*/
|
|
bool idLocalization::LoadDictionary( const byte* data, int dataLen, const char* fileName )
|
|
{
|
|
return languageDict.Load( data, dataLen, fileName );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLocalization::GetString
|
|
========================
|
|
*/
|
|
const char* idLocalization::GetString( const char* inString )
|
|
{
|
|
return languageDict.GetString( inString );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLocalization::FindString
|
|
========================
|
|
*/
|
|
const char* idLocalization::FindString( const char* inString )
|
|
{
|
|
return languageDict.FindString( inString );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLocalization::VerifyUTF8
|
|
========================
|
|
*/
|
|
utf8Encoding_t idLocalization::VerifyUTF8( const uint8* buffer, const int bufferLen, const char* name )
|
|
{
|
|
utf8Encoding_t encoding;
|
|
idStr::IsValidUTF8( buffer, bufferLen, encoding );
|
|
if( encoding == UTF8_INVALID )
|
|
{
|
|
idLib::FatalError( "Language file %s is not valid UTF-8 or plain ASCII.", name );
|
|
}
|
|
else if( encoding == UTF8_INVALID_BOM )
|
|
{
|
|
idLib::FatalError( "Language file %s is marked as UTF-8 but has invalid encoding.", name );
|
|
}
|
|
else if( encoding == UTF8_ENCODED_NO_BOM )
|
|
{
|
|
idLib::FatalError( "Language file %s has no byte order marker. Fix this or roll back to a version that has the marker.", name );
|
|
}
|
|
else if( encoding != UTF8_ENCODED_BOM && encoding != UTF8_PURE_ASCII )
|
|
{
|
|
idLib::FatalError( "Language file %s has unknown utf8Encoding_t.", name );
|
|
}
|
|
return encoding;
|
|
}
|
|
|
|
// string entries can refer to other string entries,
|
|
// recursing up to this many times before we decided someone did something stupid
|
|
const char* idLangDict::KEY_PREFIX = "#str_"; // all keys should be prefixed with this for redirection to work
|
|
const int idLangDict::KEY_PREFIX_LEN = idStr::Length( KEY_PREFIX );
|
|
|
|
/*
|
|
========================
|
|
idLangDict::idLangDict
|
|
========================
|
|
*/
|
|
idLangDict::idLangDict() : keyIndex( 4096, 4096 )
|
|
{
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::~idLangDict
|
|
========================
|
|
*/
|
|
idLangDict::~idLangDict()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::Clear
|
|
========================
|
|
*/
|
|
void idLangDict::Clear()
|
|
{
|
|
//mem.PushHeap();
|
|
for( int i = 0; i < keyVals.Num(); i++ )
|
|
{
|
|
if( keyVals[i].value == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
blockAlloc.Free( keyVals[i].value );
|
|
keyVals[i].value = NULL;
|
|
}
|
|
//mem.PopHeap();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::Load
|
|
========================
|
|
*/
|
|
bool idLangDict::Load( const byte* buffer, const int bufferLen, const char* name )
|
|
{
|
|
|
|
if( buffer == NULL || bufferLen <= 0 )
|
|
{
|
|
// let whoever called us deal with the failure (so sys_lang can be reset)
|
|
return false;
|
|
}
|
|
|
|
idLib::Printf( "Reading %s", name );
|
|
|
|
bool utf8 = false;
|
|
|
|
// in all but retail builds, ensure that the byte-order mark is NOT MISSING so that
|
|
// we can avoid debugging UTF-8 code
|
|
#ifndef ID_RETAIL
|
|
utf8Encoding_t encoding = idLocalization::VerifyUTF8( buffer, bufferLen, name );
|
|
if( encoding == UTF8_ENCODED_BOM )
|
|
{
|
|
utf8 = true;
|
|
}
|
|
else if( encoding == UTF8_PURE_ASCII )
|
|
{
|
|
utf8 = false;
|
|
}
|
|
else
|
|
{
|
|
assert( false ); // this should have been handled in VerifyUTF8 with a FatalError
|
|
return false;
|
|
}
|
|
#else
|
|
// in release we just check the BOM so we're not scanning the lang file twice on startup
|
|
if( bufferLen > 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF )
|
|
{
|
|
utf8 = true;
|
|
}
|
|
#endif
|
|
|
|
if( utf8 )
|
|
{
|
|
idLib::Printf( " as UTF-8\n" );
|
|
}
|
|
else
|
|
{
|
|
idLib::Printf( " as ASCII\n" );
|
|
}
|
|
|
|
idStr tempKey;
|
|
idStr tempVal;
|
|
|
|
int line = 0;
|
|
int numStrings = 0;
|
|
|
|
int i = 0;
|
|
while( i < bufferLen )
|
|
{
|
|
uint32 c = buffer[i++];
|
|
if( c == '/' ) // comment, read until new line
|
|
{
|
|
while( i < bufferLen )
|
|
{
|
|
c = buffer[i++];
|
|
if( c == '\n' )
|
|
{
|
|
line++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if( c == '}' )
|
|
{
|
|
break;
|
|
}
|
|
else if( c == '\n' )
|
|
{
|
|
line++;
|
|
}
|
|
else if( c == '\"' )
|
|
{
|
|
int keyStart = i;
|
|
int keyEnd = -1;
|
|
while( i < bufferLen )
|
|
{
|
|
c = buffer[i++];
|
|
if( c == '\"' )
|
|
{
|
|
keyEnd = i - 1;
|
|
break;
|
|
}
|
|
}
|
|
if( keyEnd < keyStart )
|
|
{
|
|
idLib::FatalError( "%s File ended while reading key at line %d", name, line );
|
|
}
|
|
tempKey.CopyRange( ( char* )buffer, keyStart, keyEnd );
|
|
|
|
int valStart = -1;
|
|
while( i < bufferLen )
|
|
{
|
|
c = buffer[i++];
|
|
if( c == '\"' )
|
|
{
|
|
valStart = i;
|
|
break;
|
|
}
|
|
}
|
|
if( valStart < 0 )
|
|
{
|
|
idLib::FatalError( "%s File ended while reading value at line %d", name, line );
|
|
}
|
|
int valEnd = -1;
|
|
tempVal.CapLength( 0 );
|
|
while( i < bufferLen )
|
|
{
|
|
c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++];
|
|
if( !utf8 && c >= 0x80 )
|
|
{
|
|
// this is a serious error and we must check this to avoid accidentally shipping a file where someone squased UTF-8 encodings
|
|
idLib::FatalError( "Language file %s is supposed to be plain ASCII, but has byte values > 127!", name );
|
|
}
|
|
if( c == '\"' )
|
|
{
|
|
valEnd = i - 1;
|
|
continue;
|
|
}
|
|
if( c == '\n' )
|
|
{
|
|
line++;
|
|
break;
|
|
}
|
|
if( c == '\r' )
|
|
{
|
|
continue;
|
|
}
|
|
if( c == '\\' )
|
|
{
|
|
c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++];
|
|
if( c == 'n' )
|
|
{
|
|
c = '\n';
|
|
}
|
|
else if( c == 't' )
|
|
{
|
|
c = '\t';
|
|
}
|
|
else if( c == '\"' )
|
|
{
|
|
c = '\"';
|
|
}
|
|
else if( c == '\\' )
|
|
{
|
|
c = '\\';
|
|
}
|
|
else
|
|
{
|
|
idLib::Warning( "Unknown escape sequence %x at line %d", c, line );
|
|
}
|
|
}
|
|
tempVal.AppendUTF8Char( c );
|
|
}
|
|
if( valEnd < valStart )
|
|
{
|
|
idLib::FatalError( "%s File ended while reading value at line %d", name, line );
|
|
}
|
|
if( lang_maskLocalizedStrings.GetBool() && tempVal.Length() > 0 && tempKey.Find( "#font_" ) == -1 )
|
|
{
|
|
int len = tempVal.Length();
|
|
if( len > 0 )
|
|
{
|
|
tempVal.Fill( 'W', len - 1 );
|
|
}
|
|
else
|
|
{
|
|
tempVal.Empty();
|
|
}
|
|
tempVal.Append( 'X' );
|
|
}
|
|
AddKeyVal( tempKey, tempVal );
|
|
numStrings++;
|
|
}
|
|
}
|
|
|
|
idLib::Printf( "%i strings read\n", numStrings );
|
|
|
|
// get rid of any waste due to geometric list growth
|
|
//mem.PushHeap();
|
|
keyVals.Condense();
|
|
//mem.PopHeap();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::Save
|
|
========================
|
|
*/
|
|
bool idLangDict::Save( const char* fileName )
|
|
{
|
|
idFile* outFile = fileSystem->OpenFileWrite( fileName );
|
|
if( outFile == NULL )
|
|
{
|
|
idLib::Warning( "Error saving: %s", fileName );
|
|
return false;
|
|
}
|
|
byte bof[3] = { 0xEF, 0xBB, 0xBF };
|
|
outFile->Write( bof, 3 );
|
|
outFile->WriteFloatString( "// string table\n//\n\n{\n" );
|
|
for( int j = 0; j < keyVals.Num(); j++ )
|
|
{
|
|
const idLangKeyValue& kvp = keyVals[j];
|
|
if( kvp.value == NULL )
|
|
{
|
|
continue;
|
|
}
|
|
outFile->WriteFloatString( "\t\"%s\"\t\"", kvp.key );
|
|
for( int k = 0; kvp.value[k] != 0; k++ )
|
|
{
|
|
char ch = kvp.value[k];
|
|
if( ch == '\t' )
|
|
{
|
|
outFile->Write( "\\t", 2 );
|
|
}
|
|
else if( ch == '\n' || ch == '\r' )
|
|
{
|
|
outFile->Write( "\\n", 2 );
|
|
}
|
|
else if( ch == '"' )
|
|
{
|
|
outFile->Write( "\\\"", 2 );
|
|
}
|
|
else if( ch == '\\' )
|
|
{
|
|
outFile->Write( "\\\\", 2 );
|
|
}
|
|
else
|
|
{
|
|
outFile->Write( &ch, 1 );
|
|
}
|
|
}
|
|
outFile->WriteFloatString( "\"\n" );
|
|
}
|
|
outFile->WriteFloatString( "\n}\n" );
|
|
delete outFile;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::GetString
|
|
========================
|
|
*/
|
|
const char* idLangDict::GetString( const char* str ) const
|
|
{
|
|
const char* localized = FindString( str );
|
|
if( localized == NULL )
|
|
{
|
|
return str;
|
|
}
|
|
return localized;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::FindStringIndex
|
|
========================
|
|
*/
|
|
int idLangDict::FindStringIndex( const char* str ) const
|
|
{
|
|
if( str == NULL )
|
|
{
|
|
return -1;
|
|
}
|
|
int hash = idStr::IHash( str );
|
|
for( int i = keyIndex.GetFirst( hash ); i >= 0; i = keyIndex.GetNext( i ) )
|
|
{
|
|
if( idStr::Icmp( str, keyVals[i].key ) == 0 )
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::FindString_r
|
|
========================
|
|
*/
|
|
const char* idLangDict::FindString_r( const char* str, int& depth ) const
|
|
{
|
|
depth++;
|
|
if( depth > MAX_REDIRECTION_DEPTH )
|
|
{
|
|
// This isn't an error because we assume the error will be obvious somewhere in a GUI or something,
|
|
// and the whole point of tracking the depth is to avoid a crash.
|
|
idLib::Warning( "String '%s', indirection depth > %d", str, MAX_REDIRECTION_DEPTH );
|
|
return NULL;
|
|
}
|
|
|
|
if( str == NULL || str[0] == '\0' )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
int index = FindStringIndex( str );
|
|
if( index < 0 )
|
|
{
|
|
return NULL;
|
|
}
|
|
const char* value = keyVals[index].value;
|
|
if( value == NULL )
|
|
{
|
|
return NULL;
|
|
}
|
|
if( IsStringId( value ) )
|
|
{
|
|
// this string is re-directed to another entry
|
|
return FindString_r( value, depth );
|
|
}
|
|
return value;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::FindString
|
|
========================
|
|
*/
|
|
const char* idLangDict::FindString( const char* str ) const
|
|
{
|
|
int depth = 0;
|
|
return FindString_r( str, depth );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::DeleteString
|
|
========================
|
|
*/
|
|
bool idLangDict::DeleteString( const char* key )
|
|
{
|
|
return DeleteString( FindStringIndex( key ) );
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::DeleteString
|
|
========================
|
|
*/
|
|
bool idLangDict::DeleteString( const int idx )
|
|
{
|
|
if( idx < 0 || idx >= keyVals.Num() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//mem.PushHeap();
|
|
blockAlloc.Free( keyVals[idx].value );
|
|
keyVals[idx].value = NULL;
|
|
//mem.PopHeap();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::RenameStringKey
|
|
========================
|
|
*/
|
|
bool idLangDict::RenameStringKey( const char* oldKey, const char* newKey )
|
|
{
|
|
int index = FindStringIndex( oldKey );
|
|
if( index < 0 )
|
|
{
|
|
return false;
|
|
}
|
|
//mem.PushHeap();
|
|
blockAlloc.Free( keyVals[index].key );
|
|
int newKeyLen = idStr::Length( newKey );
|
|
keyVals[index].key = blockAlloc.Alloc( newKeyLen + 1 );
|
|
idStr::Copynz( keyVals[index].key, newKey, newKeyLen + 1 );
|
|
int oldHash = idStr::IHash( oldKey );
|
|
int newHash = idStr::IHash( newKey );
|
|
if( oldHash != newHash )
|
|
{
|
|
keyIndex.Remove( oldHash, index );
|
|
keyIndex.Add( newHash, index );
|
|
}
|
|
//mem.PopHeap();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::SetString
|
|
========================
|
|
*/
|
|
bool idLangDict::SetString( const char* key, const char* val )
|
|
{
|
|
int index = FindStringIndex( key );
|
|
if( index < 0 )
|
|
{
|
|
return false;
|
|
}
|
|
//mem.PushHeap();
|
|
if( keyVals[index].value != NULL )
|
|
{
|
|
blockAlloc.Free( keyVals[index].value );
|
|
}
|
|
int valLen = idStr::Length( val );
|
|
keyVals[index].value = blockAlloc.Alloc( valLen + 1 );
|
|
idStr::Copynz( keyVals[index].value, val, valLen + 1 );
|
|
//mem.PopHeap();
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::AddKeyVal
|
|
========================
|
|
*/
|
|
void idLangDict::AddKeyVal( const char* key, const char* val )
|
|
{
|
|
if( SetString( key, val ) )
|
|
{
|
|
return;
|
|
}
|
|
//mem.PushHeap();
|
|
int keyLen = idStr::Length( key );
|
|
char* k = blockAlloc.Alloc( keyLen + 1 );
|
|
idStr::Copynz( k, key, keyLen + 1 );
|
|
char* v = NULL;
|
|
if( val != NULL )
|
|
{
|
|
int valLen = idStr::Length( val );
|
|
v = blockAlloc.Alloc( valLen + 1 );
|
|
idStr::Copynz( v, val, valLen + 1 );
|
|
}
|
|
int index = keyVals.Append( idLangKeyValue( k, v ) );
|
|
int hash = idStr::IHash( key );
|
|
keyIndex.Add( hash, index );
|
|
//mem.PopHeap();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::AddString
|
|
========================
|
|
*/
|
|
const char* idLangDict::AddString( const char* val )
|
|
{
|
|
int i = Sys_Milliseconds();
|
|
idStr key;
|
|
sprintf( key, "#str_%06d", ( i++ % 1000000 ) );
|
|
while( FindStringIndex( key ) > 0 )
|
|
{
|
|
sprintf( key, "#str_%06d", ( i++ % 1000000 ) );
|
|
}
|
|
AddKeyVal( key, val );
|
|
int index = FindStringIndex( key );
|
|
return keyVals[index].key;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::GetNumKeyVals
|
|
========================
|
|
*/
|
|
int idLangDict::GetNumKeyVals() const
|
|
{
|
|
return keyVals.Num();
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::GetKeyVal
|
|
========================
|
|
*/
|
|
const idLangKeyValue* idLangDict::GetKeyVal( int i ) const
|
|
{
|
|
return &keyVals[i];
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::IsStringId
|
|
========================
|
|
*/
|
|
bool idLangDict::IsStringId( const char* str )
|
|
{
|
|
return idStr::Icmpn( str, KEY_PREFIX, KEY_PREFIX_LEN ) == 0;
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idLangDict::GetLocalizedString
|
|
========================
|
|
*/
|
|
const char* idLangDict::GetLocalizedString( const idStrId& strId ) const
|
|
{
|
|
if( strId.GetIndex() >= 0 && strId.GetIndex() < keyVals.Num() )
|
|
{
|
|
if( keyVals[ strId.GetIndex() ].value == NULL )
|
|
{
|
|
return keyVals[ strId.GetIndex() ].key;
|
|
}
|
|
else
|
|
{
|
|
return keyVals[ strId.GetIndex() ].value;
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/*
|
|
================================================================================================
|
|
idStrId
|
|
================================================================================================
|
|
*/
|
|
|
|
/*
|
|
========================
|
|
idStrId::Set
|
|
========================
|
|
*/
|
|
void idStrId::Set( const char* key )
|
|
{
|
|
if( key == NULL || key[0] == 0 )
|
|
{
|
|
index = -1;
|
|
}
|
|
else
|
|
{
|
|
index = idLocalization::languageDict.FindStringIndex( key );
|
|
if( index < 0 )
|
|
{
|
|
// don't allow setting of string ID's to an unknown ID... this should only be allowed from
|
|
// the string table tool because additions from anywhere else are not guaranteed to be
|
|
// saved to the .lang file.
|
|
idLib::Warning( "Attempted to set unknown string ID '%s'", key );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idStrId::GetKey
|
|
========================
|
|
*/
|
|
const char* idStrId::GetKey() const
|
|
{
|
|
if( index >= 0 && index < idLocalization::languageDict.keyVals.Num() )
|
|
{
|
|
return idLocalization::languageDict.keyVals[index].key;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/*
|
|
========================
|
|
idStrId::GetLocalizedString
|
|
========================
|
|
*/
|
|
const char* idStrId::GetLocalizedString() const
|
|
{
|
|
return idLocalization::languageDict.GetLocalizedString( *this );
|
|
}
|
|
|