DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

LangDict.cpp (15985B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
     10 it under the terms of the GNU General Public License as published by
     11 the Free Software Foundation, either version 3 of the License, or
     12 (at your option) any later version.
     13 
     14 Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
     15 but WITHOUT ANY WARRANTY; without even the implied warranty of
     16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17 GNU General Public License for more details.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 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.
     23 
     24 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.
     25 
     26 ===========================================================================
     27 */
     28 #pragma hdrstop
     29 #include "precompiled.h"
     30 
     31 // 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
     32 idLangDict	idLocalization::languageDict;
     33 
     34 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." );
     35 
     36 /*
     37 ========================
     38 idLocalization::ClearDictionary
     39 ========================
     40 */
     41 void idLocalization::ClearDictionary() {
     42 	languageDict.Clear();
     43 }
     44 
     45 /*
     46 ========================
     47 idLocalization::LoadDictionary
     48 ========================
     49 */
     50 bool idLocalization::LoadDictionary( const byte * data, int dataLen, const char * fileName ) {
     51 	return languageDict.Load( data, dataLen, fileName );
     52 }
     53 
     54 /*
     55 ========================
     56 idLocalization::GetString 
     57 ========================
     58 */
     59 const char * idLocalization::GetString( const char * inString ) {
     60 	return languageDict.GetString( inString );
     61 }
     62 
     63 /*
     64 ========================
     65 idLocalization::FindString 
     66 ========================
     67 */
     68 const char * idLocalization::FindString( const char * inString ) {
     69 	return languageDict.FindString( inString );
     70 }
     71 
     72 /*
     73 ========================
     74 idLocalization::VerifyUTF8
     75 ========================
     76 */
     77 utf8Encoding_t idLocalization::VerifyUTF8( const uint8 * buffer, const int bufferLen, const char * name ) {
     78 	utf8Encoding_t encoding;
     79 	idStr::IsValidUTF8( buffer, bufferLen, encoding );
     80 	if ( encoding == UTF8_INVALID ) {
     81 		idLib::FatalError( "Language file %s is not valid UTF-8 or plain ASCII.", name );
     82 	} else if ( encoding == UTF8_INVALID_BOM ) {
     83 		idLib::FatalError( "Language file %s is marked as UTF-8 but has invalid encoding.", name );
     84 	} else if ( encoding == UTF8_ENCODED_NO_BOM ) {
     85 		idLib::FatalError( "Language file %s has no byte order marker. Fix this or roll back to a version that has the marker.", name );
     86 	} else if ( encoding != UTF8_ENCODED_BOM && encoding != UTF8_PURE_ASCII ) {
     87 		idLib::FatalError( "Language file %s has unknown utf8Encoding_t.", name );
     88 	}
     89 	return encoding;
     90 }
     91 
     92 // string entries can refer to other string entries, 
     93 // recursing up to this many times before we decided someone did something stupid
     94 const char * idLangDict::KEY_PREFIX = "#str_";	// all keys should be prefixed with this for redirection to work
     95 const int idLangDict::KEY_PREFIX_LEN = idStr::Length( KEY_PREFIX );
     96 
     97 /*
     98 ========================
     99 idLangDict::idLangDict
    100 ========================
    101 */
    102 idLangDict::idLangDict() : keyIndex( 4096, 4096 ) {
    103 }
    104 
    105 /*
    106 ========================
    107 idLangDict::~idLangDict
    108 ========================
    109 */
    110 idLangDict::~idLangDict() {
    111 	Clear();
    112 }
    113 
    114 /*
    115 ========================
    116 idLangDict::Clear
    117 ========================
    118 */
    119 void idLangDict::Clear() {
    120 	//mem.PushHeap();
    121 	for ( int i = 0; i < keyVals.Num(); i++ ) {
    122 		if ( keyVals[i].value == NULL ) {
    123 			continue;
    124 		}
    125 		blockAlloc.Free( keyVals[i].value );
    126 		keyVals[i].value = NULL;
    127 	}
    128 	//mem.PopHeap();
    129 }
    130 
    131 /*
    132 ========================
    133 idLangDict::Load
    134 ========================
    135 */
    136 bool idLangDict::Load( const byte * buffer, const int bufferLen, const char *name ) {
    137 
    138 	if ( buffer == NULL || bufferLen <= 0 ) {
    139 		// let whoever called us deal with the failure (so sys_lang can be reset)
    140 		return false;
    141 	}
    142 
    143 	idLib::Printf( "Reading %s", name );
    144 
    145 	bool utf8 = false;
    146 
    147 	// in all but retail builds, ensure that the byte-order mark is NOT MISSING so that
    148 	// we can avoid debugging UTF-8 code
    149 #ifndef ID_RETAIL
    150 	utf8Encoding_t encoding = idLocalization::VerifyUTF8( buffer, bufferLen, name );
    151 	if ( encoding == UTF8_ENCODED_BOM ) {
    152 		utf8 = true;
    153 	} else if ( encoding == UTF8_PURE_ASCII ) {
    154 		utf8 = false;
    155 	} else {
    156 		assert( false );	// this should have been handled in VerifyUTF8 with a FatalError
    157 		return false;
    158 	}
    159 #else
    160 	// in release we just check the BOM so we're not scanning the lang file twice on startup
    161 	if ( bufferLen > 3 && buffer[0] == 0xEF && buffer[1] == 0xBB && buffer[2] == 0xBF ) {
    162 		utf8 = true;
    163 	}
    164 #endif
    165 
    166 	if ( utf8 ) {
    167 		idLib::Printf( " as UTF-8\n" );
    168 	} else {
    169 		idLib::Printf( " as ASCII\n" );
    170 	}
    171 
    172 	idStr tempKey;
    173 	idStr tempVal;
    174 
    175 	int line = 0;
    176 	int numStrings = 0;
    177 
    178 	int i = 0;
    179 	while ( i < bufferLen ) {
    180 		uint32 c = buffer[i++];
    181 		if ( c == '/' ) { // comment, read until new line
    182 			while ( i < bufferLen ) {
    183 				c = buffer[i++];
    184 				if ( c == '\n' ) {
    185 					line++;
    186 					break;
    187 				}
    188 			}
    189 		} else if ( c == '}' ) {
    190 			break;
    191 		} else if ( c == '\n' ) {
    192 			line++;
    193 		} else if ( c == '\"' ) {
    194 			int keyStart = i;
    195 			int keyEnd = -1;
    196 			while ( i < bufferLen ) {
    197 				c = buffer[i++];
    198 				if ( c == '\"' ) {
    199 					keyEnd = i - 1;
    200 					break;
    201 				}
    202 			}
    203 			if ( keyEnd < keyStart ) {
    204 				idLib::FatalError( "%s File ended while reading key at line %d", name, line );
    205 			}
    206 			tempKey.CopyRange( (char *)buffer, keyStart, keyEnd );
    207 
    208 			int valStart = -1;
    209 			while ( i < bufferLen ) {
    210 				c = buffer[i++];
    211 				if ( c == '\"' ) {
    212 					valStart = i;
    213 					break;
    214 				}
    215 			}
    216 			if ( valStart < 0 ) {
    217 				idLib::FatalError( "%s File ended while reading value at line %d", name, line );
    218 			}
    219 			int valEnd = -1;
    220 			tempVal.CapLength( 0 );
    221 			while ( i < bufferLen ) {
    222 				c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++];
    223 				if ( !utf8 && c >= 0x80 ) {
    224 					// this is a serious error and we must check this to avoid accidentally shipping a file where someone squased UTF-8 encodings
    225 					idLib::FatalError( "Language file %s is supposed to be plain ASCII, but has byte values > 127!", name );
    226 				}
    227 				if ( c == '\"' ) {
    228 					valEnd = i - 1;
    229 					continue;
    230 				}
    231 				if ( c == '\n' ) {
    232 					line++;
    233 					break;
    234 				}
    235 				if ( c == '\r' ) {
    236 					continue;
    237 				}
    238 				if ( c == '\\' ) {
    239 					c = utf8 ? idStr::UTF8Char( buffer, i ) : buffer[i++];
    240 					if ( c == 'n' ) {
    241 						c = '\n';
    242 					} else if ( c == 't' ) {
    243 						c = '\t';
    244 					} else if ( c == '\"' ) {
    245 						c = '\"';
    246 					} else if ( c == '\\' ) {
    247 						c = '\\';
    248 					} else {
    249 						idLib::Warning( "Unknown escape sequence %x at line %d", c, line );
    250 					}
    251 				}
    252 				tempVal.AppendUTF8Char( c );
    253 			}
    254 			if ( valEnd < valStart ) {
    255 				idLib::FatalError( "%s File ended while reading value at line %d", name, line );
    256 			}
    257 			if ( lang_maskLocalizedStrings.GetBool() && tempVal.Length() > 0 && tempKey.Find( "#font_" ) == -1 ) {
    258 				int len = tempVal.Length();
    259 				if ( len > 0 ) {
    260 					tempVal.Fill( 'W', len - 1 );
    261 				} else {
    262 					tempVal.Empty();
    263 				}
    264 				tempVal.Append( 'X' );
    265 			}
    266 			AddKeyVal( tempKey, tempVal );
    267 			numStrings++;
    268 		}
    269 	}
    270 
    271 	idLib::Printf( "%i strings read\n", numStrings );
    272 	
    273 	// get rid of any waste due to geometric list growth
    274 	//mem.PushHeap();
    275 	keyVals.Condense();
    276 	//mem.PopHeap();
    277 
    278 	return true;
    279 }
    280 
    281 /*
    282 ========================
    283 idLangDict::Save
    284 ========================
    285 */
    286 bool idLangDict::Save( const char * fileName ) {
    287 	idFile * outFile = fileSystem->OpenFileWrite( fileName );
    288 	if ( outFile == NULL ) {
    289 		idLib::Warning( "Error saving: %s", fileName );
    290 		return false;
    291 	}
    292 	byte bof[3] = { 0xEF, 0xBB, 0xBF };
    293 	outFile->Write( bof, 3 );
    294 	outFile->WriteFloatString( "// string table\n//\n\n{\n" );
    295 	for ( int j = 0; j < keyVals.Num(); j++ ) {
    296 		const idLangKeyValue & kvp = keyVals[j];
    297 		if ( kvp.value == NULL ) {
    298 			continue;
    299 		}
    300 		outFile->WriteFloatString( "\t\"%s\"\t\"", kvp.key );
    301 		for ( int k = 0; kvp.value[k] != 0; k++ ) {
    302 			char ch = kvp.value[k];
    303 			if ( ch == '\t' ) {
    304 				outFile->Write( "\\t", 2 );
    305 			} else if ( ch == '\n' || ch == '\r' ) {
    306 				outFile->Write( "\\n", 2 );
    307 			} else if ( ch == '"' ) {
    308 				outFile->Write( "\\\"", 2 );
    309 			} else if ( ch == '\\' ) {
    310 				outFile->Write( "\\\\", 2 );
    311 			} else {
    312 				outFile->Write( &ch, 1 );
    313 			}
    314 		}
    315 		outFile->WriteFloatString( "\"\n" );
    316 	}
    317 	outFile->WriteFloatString( "\n}\n" );
    318 	delete outFile;
    319 	return true;
    320 }
    321 
    322 /*
    323 ========================
    324 idLangDict::GetString
    325 ========================
    326 */
    327 const char * idLangDict::GetString( const char * str ) const {
    328 	const char * localized = FindString( str );
    329 	if ( localized == NULL ) {
    330 		return str;
    331 	}
    332 	return localized;
    333 }
    334 
    335 /*
    336 ========================
    337 idLangDict::FindStringIndex
    338 ========================
    339 */
    340 int idLangDict::FindStringIndex( const char * str ) const {
    341 	if ( str == NULL ) {
    342 		return -1;
    343 	}
    344 	int hash = idStr::IHash( str );
    345 	for ( int i = keyIndex.GetFirst( hash ); i >= 0; i = keyIndex.GetNext( i ) ) {
    346 		if ( idStr::Icmp( str, keyVals[i].key ) == 0 ) {
    347 			return i;
    348 		}
    349 	}
    350 	return -1;
    351 }
    352 
    353 /*
    354 ========================
    355 idLangDict::FindString_r
    356 ========================
    357 */
    358 const char * idLangDict::FindString_r( const char * str, int & depth ) const {
    359 	depth++;
    360 	if ( depth > MAX_REDIRECTION_DEPTH ) {
    361 		// This isn't an error because we assume the error will be obvious somewhere in a GUI or something,
    362 		// and the whole point of tracking the depth is to avoid a crash.
    363 		idLib::Warning( "String '%s', indirection depth > %d", str, MAX_REDIRECTION_DEPTH );
    364 		return NULL;
    365 	}
    366 
    367 	if ( str == NULL || str[0] == '\0' ) {
    368 		return NULL;
    369 	}
    370 
    371 	int index = FindStringIndex( str );
    372 	if ( index < 0 ) {
    373 		return NULL;
    374 	}
    375 	const char * value = keyVals[index].value;
    376 	if ( value == NULL ) {
    377 		return NULL;
    378 	}
    379 	if ( IsStringId( value ) ) {
    380 		// this string is re-directed to another entry
    381 		return FindString_r( value, depth );
    382 	}
    383 	return value;
    384 }
    385 
    386 /*
    387 ========================
    388 idLangDict::FindString
    389 ========================
    390 */
    391 const char * idLangDict::FindString( const char * str ) const {
    392 	int depth = 0;
    393 	return FindString_r( str, depth );
    394 }
    395 
    396 /*
    397 ========================
    398 idLangDict::DeleteString
    399 ========================
    400 */
    401 bool idLangDict::DeleteString( const char * key ) {
    402 	return DeleteString( FindStringIndex( key ) );
    403 }
    404 
    405 /*
    406 ========================
    407 idLangDict::DeleteString
    408 ========================
    409 */
    410 bool idLangDict::DeleteString( const int idx ) {
    411 	if ( idx < 0 || idx >= keyVals.Num() ) {
    412 		return false;
    413 	}
    414 	
    415 	//mem.PushHeap();
    416 	blockAlloc.Free( keyVals[idx].value );
    417 	keyVals[idx].value = NULL;
    418 	//mem.PopHeap();
    419 
    420 	return true;
    421 }
    422 
    423 /*
    424 ========================
    425 idLangDict::RenameStringKey
    426 ========================
    427 */
    428 bool idLangDict::RenameStringKey( const char * oldKey, const char * newKey ) {
    429 	int index = FindStringIndex( oldKey );
    430 	if ( index < 0 ) {
    431 		return false;
    432 	}
    433 	//mem.PushHeap();
    434 	blockAlloc.Free( keyVals[index].key );
    435 	int newKeyLen = idStr::Length( newKey );
    436 	keyVals[index].key = blockAlloc.Alloc( newKeyLen + 1 );
    437 	idStr::Copynz( keyVals[index].key, newKey, newKeyLen + 1 );
    438 	int oldHash = idStr::IHash( oldKey );
    439 	int newHash = idStr::IHash( newKey );
    440 	if ( oldHash != newHash ) {
    441 		keyIndex.Remove( oldHash, index );
    442 		keyIndex.Add( newHash, index );
    443 	}
    444 	//mem.PopHeap();
    445 
    446 	return true;
    447 }
    448 
    449 /*
    450 ========================
    451 idLangDict::SetString
    452 ========================
    453 */
    454 bool idLangDict::SetString( const char * key, const char * val ) {
    455 	int index = FindStringIndex( key );
    456 	if ( index < 0 ) {
    457 		return false;
    458 	}
    459 	//mem.PushHeap();
    460 	if ( keyVals[index].value != NULL ) {
    461 		blockAlloc.Free( keyVals[index].value );
    462 	}
    463 	int valLen = idStr::Length( val );
    464 	keyVals[index].value = blockAlloc.Alloc( valLen + 1 );
    465 	idStr::Copynz( keyVals[index].value, val, valLen + 1 );
    466 	//mem.PopHeap();
    467 	return true;
    468 }
    469 
    470 /*
    471 ========================
    472 idLangDict::AddKeyVal
    473 ========================
    474 */
    475 void idLangDict::AddKeyVal( const char * key, const char * val ) {
    476 	if ( SetString( key, val ) ) {
    477 		return;
    478 	}
    479 	//mem.PushHeap();
    480 	int keyLen = idStr::Length( key );
    481 	char * k = blockAlloc.Alloc( keyLen + 1 );
    482 	idStr::Copynz( k, key, keyLen + 1 );
    483 	char * v = NULL;
    484 	if ( val != NULL ) {
    485 		int valLen = idStr::Length( val );
    486 		v = blockAlloc.Alloc( valLen + 1 );
    487 		idStr::Copynz( v, val, valLen + 1 );
    488 	}
    489 	int index = keyVals.Append( idLangKeyValue( k, v ) );
    490 	int hash = idStr::IHash( key );
    491 	keyIndex.Add( hash, index );
    492 	//mem.PopHeap();
    493 }
    494 
    495 /*
    496 ========================
    497 idLangDict::AddString
    498 ========================
    499 */
    500 const char * idLangDict::AddString( const char * val ) {
    501 	int i = Sys_Milliseconds();
    502 	idStr key;
    503 	sprintf( key, "#str_%06d", ( i++ % 1000000 ) );
    504 	while ( FindStringIndex( key ) > 0 ) {
    505 		sprintf( key, "#str_%06d", ( i++ % 1000000 ) );
    506 	}
    507 	AddKeyVal( key, val );
    508 	int index = FindStringIndex( key );
    509 	return keyVals[index].key;
    510 }
    511 
    512 /*
    513 ========================
    514 idLangDict::GetNumKeyVals
    515 ========================
    516 */
    517 int idLangDict::GetNumKeyVals() const {
    518 	return keyVals.Num();
    519 }
    520 
    521 /*
    522 ========================
    523 idLangDict::GetKeyVal
    524 ========================
    525 */
    526 const idLangKeyValue * idLangDict::GetKeyVal( int i ) const {
    527 	return &keyVals[i];
    528 }
    529 
    530 /*
    531 ========================
    532 idLangDict::IsStringId
    533 ========================
    534 */
    535 bool idLangDict::IsStringId( const char * str ) {
    536 	return idStr::Icmpn( str, KEY_PREFIX, KEY_PREFIX_LEN ) == 0;
    537 }
    538 
    539 /*
    540 ========================
    541 idLangDict::GetLocalizedString
    542 ========================
    543 */
    544 const char * idLangDict::GetLocalizedString( const idStrId & strId ) const {
    545 	if ( strId.GetIndex() >= 0 && strId.GetIndex() < keyVals.Num() ) {
    546 		if ( keyVals[ strId.GetIndex() ].value == NULL ) {
    547 			return keyVals[ strId.GetIndex() ].key;
    548 		} else {
    549 			return keyVals[ strId.GetIndex() ].value;
    550 		}
    551 	}
    552 	return "";
    553 }
    554 
    555 /*
    556 ================================================================================================
    557 idStrId
    558 ================================================================================================
    559 */
    560 
    561 /*
    562 ========================
    563 idStrId::Set
    564 ========================
    565 */
    566 void idStrId::Set( const char * key ) {
    567 	if ( key == NULL || key[0] == 0 ) {
    568 		index = -1;
    569 	} else {
    570 		index = idLocalization::languageDict.FindStringIndex( key );
    571 		if ( index < 0 ) {
    572 			// don't allow setting of string ID's to an unknown ID... this should only be allowed from 
    573 			// the string table tool because additions from anywhere else are not guaranteed to be 
    574 			// saved to the .lang file.
    575 			idLib::Warning( "Attempted to set unknown string ID '%s'", key );
    576 		}
    577 	}
    578 }
    579 
    580 /*
    581 ========================
    582 idStrId::GetKey
    583 ========================
    584 */
    585 const char * idStrId::GetKey() const {
    586 	if ( index >= 0 && index < idLocalization::languageDict.keyVals.Num() ) {
    587 		return idLocalization::languageDict.keyVals[index].key;
    588 	}
    589 	return "";
    590 }
    591 
    592 /*
    593 ========================
    594 idStrId::GetLocalizedString
    595 ========================
    596 */
    597 const char * idStrId::GetLocalizedString() const {
    598 	return idLocalization::languageDict.GetLocalizedString( *this );
    599 }
    600