core/Translator.cpp
author David Anderson <dvander@alliedmods.net>
Sun Aug 26 17:41:02 2007 +0000 (2007-08-26)
changeset 1369 bd9ccced1b1a
parent 1352 4241c756c278
child 1406 f5c07ec3c052
permissions -rw-r--r--
fixed amb834 - command callbacks could potentially occur on invalid clients in very rare cases
     1 /**
     2  * vim: set ts=4 :
     3  * =============================================================================
     4  * SourceMod
     5  * Copyright (C) 2004-2007 AlliedModders LLC.  All rights reserved.
     6  * =============================================================================
     7  *
     8  * This program is free software; you can redistribute it and/or modify it under
     9  * the terms of the GNU General Public License, version 3.0, as published by the
    10  * Free Software Foundation.
    11  * 
    12  * This program is distributed in the hope that it will be useful, but WITHOUT
    13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    14  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
    15  * details.
    16  *
    17  * You should have received a copy of the GNU General Public License along with
    18  * this program.  If not, see <http://www.gnu.org/licenses/>.
    19  *
    20  * As a special exception, AlliedModders LLC gives you permission to link the
    21  * code of this program (as well as its derivative works) to "Half-Life 2," the
    22  * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
    23  * by the Valve Corporation.  You must obey the GNU General Public License in
    24  * all respects for all other code used.  Additionally, AlliedModders LLC grants
    25  * this exception to all derivative works.  AlliedModders LLC defines further
    26  * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
    27  * or <http://www.sourcemod.net/license.php>.
    28  *
    29  * Version: $Id$
    30  */
    31 
    32 #include <stdarg.h>
    33 #include <stdlib.h>
    34 #include <ctype.h>
    35 #include "Translator.h"
    36 #include "Logger.h"
    37 #include "TextParsers.h"
    38 #include "LibrarySys.h"
    39 #include "sm_stringutil.h"
    40 #include "sourcemod.h"
    41 #include "PlayerManager.h"
    42 
    43 Translator g_Translator;
    44 CPhraseFile *g_pCorePhrases = NULL;
    45 
    46 struct trans_t
    47 {
    48 	int stridx;
    49 	int fmt_order;
    50 };
    51 
    52 struct phrase_t
    53 {
    54 	int fmt_list;
    55 	unsigned int fmt_count;
    56 	unsigned int fmt_bytes;
    57 	int trans_tbl;
    58 	unsigned int translations;
    59 };
    60 
    61 CPhraseFile::CPhraseFile(Translator *pTranslator, const char *file)
    62 {
    63 	m_pStringTab = pTranslator->GetStringTable();
    64 	m_pMemory = m_pStringTab->GetMemTable();
    65 	m_LangCount = pTranslator->GetLanguageCount();
    66 	m_File.assign(file);
    67 	m_pTranslator = pTranslator;
    68 	m_pPhraseLookup = NULL;
    69 }
    70 
    71 CPhraseFile::~CPhraseFile()
    72 {
    73 	if (m_pPhraseLookup)
    74 	{
    75 		sm_trie_destroy(m_pPhraseLookup);
    76 	}
    77 }
    78 
    79 void CPhraseFile::ParseError(const char *message, ...)
    80 {
    81 	char buffer[1024];
    82 	va_list ap;
    83 	va_start(ap, message);
    84 	vsnprintf(buffer, sizeof(buffer), message, ap);
    85 	va_end(ap);
    86 
    87 	m_ParseError.assign(buffer);
    88 }
    89 
    90 void CPhraseFile::ParseWarning(const char *message, ...)
    91 {
    92 	char buffer[1024];
    93 	va_list ap;
    94 	va_start(ap, message);
    95 	vsnprintf(buffer, sizeof(buffer), message, ap);
    96 	va_end(ap);
    97 
    98 	if (!m_FileLogged)
    99 	{
   100 		g_Logger.LogError("[SM] Warning(s) encountered in translation file \"%s\"", m_File.c_str());
   101 		m_FileLogged = true;
   102 	}
   103 
   104 	g_Logger.LogError("[SM] %s", message);
   105 }
   106 
   107 void CPhraseFile::ReparseFile()
   108 {
   109 	if (m_pPhraseLookup)
   110 	{
   111 		sm_trie_destroy(m_pPhraseLookup);
   112 	}
   113 	m_pPhraseLookup = sm_trie_create();
   114 
   115 	m_LangCount = m_pTranslator->GetLanguageCount();
   116 
   117 	if (!m_LangCount)
   118 	{
   119 		return;
   120 	}
   121 
   122 	SMCParseError err;
   123 	char path[PLATFORM_MAX_PATH];
   124 	g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "translations/%s", m_File.c_str());
   125 
   126 	//backwards compatibility shim
   127 	/* :HACKHACK: Change .cfg/.txt and vice versa for compatibility */
   128 	if (!g_LibSys.PathExists(path))
   129 	{
   130 		if (m_File.compare("common.cfg") == 0)
   131 		{
   132 			UTIL_ReplaceAll(path, sizeof(path), "common.cfg", "common.phrases.txt");
   133 		} else if (strstr(path, ".cfg")) {
   134 			UTIL_ReplaceAll(path, sizeof(path), ".cfg", ".txt");
   135 		} else if (strstr(path, ".txt")) {
   136 			UTIL_ReplaceAll(path, sizeof(path), ".txt", ".cfg");
   137 		}
   138 	}
   139 
   140 	unsigned int line=0, col=0;
   141 
   142 	if ((err=g_TextParser.ParseFile_SMC(path, this, &line, &col)) != SMCParse_Okay)
   143 	{
   144 		const char *msg = g_TextParser.GetSMCErrorString(err);
   145 		if (!msg)
   146 		{
   147 			msg = m_ParseError.c_str();
   148 		}
   149 
   150 		g_Logger.LogError("[SM] Fatal error encountered parsing translation file \"%s\"", m_File.c_str());
   151 		g_Logger.LogError("[SM] Error (line %d, column %d): %s", line, col, msg);
   152 	}
   153 }
   154 
   155 void CPhraseFile::ReadSMC_ParseStart()
   156 {
   157 	m_CurPhrase = -1;
   158 	m_ParseState = PPS_None;
   159 	m_CurLine = 0;
   160 	m_FileLogged = false;
   161 	m_LastPhraseString.clear();
   162 }
   163 
   164 SMCParseResult CPhraseFile::ReadSMC_RawLine(const char *line, unsigned int curline)
   165 {
   166 	m_CurLine = curline;
   167 
   168 	return SMCParse_Continue;
   169 }
   170 
   171 SMCParseResult CPhraseFile::ReadSMC_NewSection(const char *name, bool opt_quotes)
   172 {
   173 	bool recognized = false;
   174 	if (m_ParseState == PPS_None)
   175 	{
   176 		if (strcmp(name, "Phrases") == 0)
   177 		{
   178 			m_ParseState = PPS_Phrases;
   179 			recognized = true;
   180 		}
   181 	} else if (m_ParseState == PPS_Phrases) {
   182 		m_ParseState = PPS_InPhrase;
   183 		recognized = true;
   184 
   185 		phrase_t *pPhrase;
   186 		m_CurPhrase = m_pMemory->CreateMem(sizeof(phrase_t), (void **)&pPhrase);
   187 
   188 		/* Create the reverse lookup */
   189 		if (!sm_trie_insert(m_pPhraseLookup, name, reinterpret_cast<void *>(m_CurPhrase)))
   190 		{
   191 			ParseWarning("Skipping duplicate phrase \"%s\" on line %d.", name, m_CurLine);
   192 			/* :IDEA: prevent memory waste by seeking backwards in memtable? */
   193 			m_CurPhrase = -1;
   194 		} else {
   195 			/* Initialize new phrase */
   196 			trans_t *pTrans;
   197 	
   198 			pPhrase->fmt_count = 0;
   199 			pPhrase->fmt_list = -1;
   200 
   201 			int trans_tbl = m_pMemory->CreateMem(sizeof(trans_t) * m_LangCount, (void **)&pTrans);
   202 			pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
   203 			pPhrase->trans_tbl = trans_tbl;
   204 
   205 			pPhrase->translations = 0;
   206 			pPhrase->fmt_bytes = 0;
   207 	
   208 			for (unsigned int i=0; i<m_LangCount; i++)
   209 			{
   210 				pTrans[i].stridx = -1;
   211 			}
   212 			m_LastPhraseString.assign(name);
   213 		}
   214 	} else if (m_ParseState == PPS_InPhrase) {
   215 		ParseError("Phrase sections may not have sub-sections");
   216 		return SMCParse_HaltFail;
   217 	}
   218 
   219 	if (!recognized)
   220 	{
   221 		ParseWarning("Ignoring invalid section \"%s\" on line %d.", name, m_CurLine);
   222 	}
   223 
   224 	return SMCParse_Continue;
   225 }
   226 
   227 SMCParseResult CPhraseFile::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
   228 {
   229 	/* See if we are ignoring a phrase */
   230 	if (m_CurPhrase == -1)
   231 	{
   232 		return SMCParse_Continue;
   233 	}
   234 
   235 	phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
   236 	
   237 	if (key[0] == '#' && strcmp(key, "#format") == 0)
   238 	{
   239 		if (pPhrase->fmt_list != -1)
   240 		{
   241 			ParseWarning("Ignoring duplicated #format property on line %d", m_CurLine);
   242 			return SMCParse_Continue;
   243 		}
   244 
   245 		if (pPhrase->translations > 0)
   246 		{
   247 			ParseWarning("#format property should come before translations on line %d, ignoring", m_CurLine);
   248 			return SMCParse_Continue;
   249 		}
   250 
   251 		/* Do an initial browsing for error checking and what not */
   252 		enum ParseStates
   253 		{
   254 			Parse_None,
   255 			Parse_Index,
   256 			Parse_Format,
   257 		};
   258 
   259 		ParseStates state = Parse_None;
   260 
   261 		const char *old_value = value;
   262 		const char *last_value = value;
   263 		while (*value != '\0')
   264 		{
   265 			if (state == Parse_None)
   266 			{
   267 				if (*value == '{')
   268 				{
   269 					pPhrase->fmt_count++;
   270 					state = Parse_Index;
   271 				} else if (*value == ',') {
   272 					/* Do nothing */
   273 				} else {
   274 					unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
   275 					if (bytes != 1 || !isalpha(*value))
   276 					{
   277 						ParseWarning("Invalid token '%c' in #format property on line %d.", *value, m_CurLine);
   278 					}
   279 				}
   280 			} else if (state == Parse_Index) {
   281 				if (*value == ':')
   282 				{
   283 					state = Parse_Format;
   284 					if (value - last_value >= 15)
   285 					{
   286 						ParseWarning("Too many digits in format index on line %d, phrase will be ignored.", m_CurLine);
   287 						m_CurPhrase = -1;
   288 						return SMCParse_Continue;
   289 					}
   290 				} else {
   291 					unsigned int bytes = g_TextParser.GetUTF8CharBytes(value);
   292 					if (bytes != 1 || !isdigit(*value))
   293 					{
   294 						ParseWarning("Token '%c' in #format property on line %d is not a digit, phrase will be ignored.",
   295 									*value,
   296 									m_CurLine);
   297 						m_CurPhrase = -1;
   298 						return SMCParse_Continue;
   299 					}
   300 				}
   301 			} else if (state == Parse_Format) {
   302 				if (*value == '}')
   303 				{
   304 					state = Parse_None;
   305 					last_value = value + 1;
   306 				}
   307 			}
   308 			value++;
   309 		}
   310 
   311 		if (state != Parse_None)
   312 		{
   313 			/* Moose clam cow. */
   314 			ParseWarning("Unterminated format string on line %d, phrase will be ignored.", m_CurLine);
   315 			m_CurPhrase = -1;
   316 			return SMCParse_Continue;
   317 		}
   318 		value = old_value;
   319 
   320 		/* Allocate the format table */
   321 		char fmt_buf[16];
   322 		int *fmt_list;
   323 		int tmp = m_pMemory->CreateMem(sizeof(int) * pPhrase->fmt_count, (void **)&fmt_list);
   324 
   325 		/* Update the phrase pointer in case it changed */
   326 		pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
   327 		pPhrase->fmt_list = tmp;
   328 
   329 		/* Initialize */
   330 		for (size_t i=0; i<pPhrase->fmt_count; i++)
   331 		{
   332 			fmt_list[i] = -1;
   333 		}
   334 
   335 		/* Now we need to read again... this time into the format buffer */
   336 		const char *in_ptr = value;
   337 		const char *idx_ptr = NULL;
   338 		char *out_ptr = NULL;
   339 		unsigned int cur_idx = 0;
   340 		state = Parse_None;
   341 		while (*in_ptr != '\0')
   342 		{
   343 			if (state == Parse_None)
   344 			{
   345 				if (*in_ptr == '{')
   346 				{
   347 					state = Parse_Index;
   348 					idx_ptr = NULL;
   349 				}
   350 			} else if (state == Parse_Index) {
   351 				if (*in_ptr == ':')
   352 				{
   353 					/* Check the number! */
   354 					if (!idx_ptr)
   355 					{
   356 						ParseWarning("Format property contains unindexed format string on line %d, phrase will be ignored.", m_CurLine);
   357 						m_CurPhrase = -1;
   358 						return SMCParse_Continue;
   359 					}
   360 					long idx = strtol(idx_ptr, NULL, 10);
   361 					if (idx < 1 || idx > (long)pPhrase->fmt_count)
   362 					{
   363 						ParseWarning("Format property contains invalid index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
   364 						m_CurPhrase = -1;
   365 						return SMCParse_Continue;
   366 					} else if (fmt_list[idx - 1] != -1) {
   367 						ParseWarning("Format property contains duplicated index '%d' on line %d, phrase will be ignored.", idx, m_CurLine);
   368 						m_CurPhrase = -1;
   369 						return SMCParse_Continue;
   370 					}
   371 					cur_idx = (unsigned int)idx;
   372 					state = Parse_Format;
   373 					out_ptr = NULL;
   374 				} else if (!idx_ptr) {
   375 					idx_ptr = in_ptr;
   376 				}
   377 			} else if (state == Parse_Format) {
   378 				if (*in_ptr == '}')
   379 				{
   380 					if (!out_ptr)
   381 					{
   382 						ParseWarning("Format property contains empty format string on line %d, phrase will be ignored.", m_CurLine);
   383 						m_CurPhrase = -1;
   384 						return SMCParse_Continue;
   385 					}
   386 					*out_ptr = '\0';
   387 					state = Parse_None;
   388 					/* Now, add this to our table */
   389 					int tmp_idx = m_pStringTab->AddString(fmt_buf);
   390 					/* Update pointers and update necessary variables */
   391 					pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
   392 					pPhrase->fmt_bytes += strlen(fmt_buf);
   393 					fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
   394 					fmt_list[cur_idx - 1] = tmp_idx;
   395 				} else {
   396 					if (!out_ptr)
   397 					{
   398 						out_ptr = fmt_buf;
   399 						*out_ptr++ = '%';
   400 					}
   401 					/* Check length ... */
   402 					if ((unsigned)(out_ptr - fmt_buf) >= sizeof(fmt_buf) - 1)
   403 					{
   404 						ParseWarning("Format property contains format string that exceeds maximum length on line %d, phrase will be ignored.",
   405 									 m_CurLine);
   406 						m_CurPhrase = -1;
   407 						return SMCParse_Continue;
   408 					}
   409 					*out_ptr++ = *in_ptr;
   410 				}
   411 			}
   412 			in_ptr++;
   413 		}
   414 
   415 		/* If we've gotten here, we only accepted unique indexes in a range.
   416 		 * Therefore, the list has to be completely filled.  Double check anyway.
   417 		 */
   418 		for (size_t i=0; i<pPhrase->fmt_count; i++)
   419 		{
   420 			if (fmt_list[i] == -1)
   421 			{
   422 				ParseWarning("Format property contains no string for index %d on line %d, phrase will be ignored.",
   423 							 i + 1,
   424 							 m_CurLine);
   425 				m_CurPhrase = -1;
   426 				return SMCParse_Continue;
   427 			}
   428 		}
   429 	} else {
   430 		size_t len = strlen(key);
   431 		if (len != 2)
   432 		{
   433 			ParseWarning("Ignoring translation to invalid language \"%s\" on line %d.", key, m_CurLine);
   434 			return SMCParse_Continue;
   435 		}
   436 
   437 		unsigned int lang;
   438 		if (!m_pTranslator->GetLanguageByCode(key, &lang))
   439 		{
   440 			/* Ignore if we don't have a language.
   441 			 * :IDEA: issue a one-time alert?
   442 			 */
   443 			return SMCParse_Continue;
   444 		}
   445 
   446 		/* See how many bytes we need for this string, then allocate.
   447 		 * NOTE: THIS SHOULD GUARANTEE THAT WE DO NOT NEED TO NEED TO SIZE CHECK 
   448 		 */
   449 		len = strlen(value) + pPhrase->fmt_bytes + 1;
   450 		char *out_buf;
   451 		int out_idx;
   452 
   453 		out_idx = m_pMemory->CreateMem(len, (void **)&out_buf);
   454 
   455 		/* Update pointer */
   456 		pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
   457 
   458 		int *fmt_order;
   459 		int *fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
   460 		trans_t *pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
   461 
   462 		pTrans = &pTrans[lang];
   463 		pTrans->stridx = out_idx;
   464 
   465 		bool params[MAX_TRANSLATE_PARAMS];
   466 
   467 		/* Build the format order list, if necessary */
   468 		if (fmt_list)
   469 		{
   470 			int tmp = m_pMemory->CreateMem(pPhrase->fmt_count * sizeof(int), (void **)&fmt_order);
   471 			
   472 			/* Update pointers */
   473 			pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
   474 			pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
   475 			fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
   476 			pTrans = &pTrans[lang];
   477 
   478 			/* Now it's safe to save the index */
   479 			pTrans->fmt_order = tmp;
   480 
   481 			for (unsigned int i=0; i<pPhrase->fmt_count; i++)
   482 			{
   483 				fmt_order[i] = -1;
   484 			}
   485 			memset(&params[0], 0, sizeof(params));
   486 		}
   487 
   488 		const char *in_ptr = value;
   489 		char *out_ptr = out_buf;
   490 		unsigned int order_idx = 0;
   491 		while (*in_ptr != '\0')
   492 		{
   493 			if (*in_ptr == '\\')
   494 			{
   495 				switch (*(in_ptr + 1))
   496 				{
   497 				case '\n':
   498 					*out_ptr++ = '\n';
   499 					break;
   500 				case '\t':
   501 					*out_ptr++ = '\t';
   502 					break;
   503 				case '\r':
   504 					*out_ptr++ = '\r';
   505 					break;
   506 				case '{':
   507 					*out_ptr++ = '{';
   508 					break;
   509 				default:
   510 					/* Copy both bytes since we don't know what's going on */
   511 					*out_ptr++ = *in_ptr++;
   512 					*out_ptr++ = *in_ptr;
   513 					break;
   514 				}
   515 				/* Skip past the last byte read */
   516 				in_ptr++;
   517 			} else if (*in_ptr == '{' && fmt_list != NULL) {
   518 				/* Search for parameters if this is a formatted string */
   519 				const char *scrap_in_point = in_ptr;
   520 				const char *digit_start = ++in_ptr;
   521 				unsigned int bytes;
   522 				while (*in_ptr != '\0')
   523 				{
   524 					bytes = g_TextParser.GetUTF8CharBytes(in_ptr);
   525 					if (bytes != 1)
   526 					{
   527 						goto scrap_point;
   528 					}
   529 					if (*in_ptr == '}')
   530 					{
   531 						/* Did we get an index? */
   532 						if (in_ptr == digit_start)
   533 						{
   534 							goto scrap_point;
   535 						}
   536 						/* Is it valid? */
   537 						long idx = strtol(digit_start, NULL, 10);
   538 						if (idx < 1 || idx > (int)pPhrase->fmt_count)
   539 						{
   540 							goto scrap_point;
   541 						}
   542 						if (params[idx-1])
   543 						{
   544 							goto scrap_point;
   545 						}
   546 
   547 						/* We're safe to insert the string.  First mark order. */
   548 						fmt_order[order_idx++] = (int)idx - 1;
   549 						/* Now concatenate */
   550 						out_ptr += sprintf(out_ptr, "%s", m_pStringTab->GetString(fmt_list[idx-1]));
   551 						/* Mark as used */
   552 						params[idx-1] = true;
   553 
   554 						goto cont_loop;
   555 					}
   556 					in_ptr++;
   557 				}
   558 scrap_point:
   559 				/* Pretend none of this ever happened.  Move along now! */
   560 				in_ptr = scrap_in_point;
   561 				*out_ptr++ = *in_ptr;
   562 			} else {
   563 				*out_ptr++ = *in_ptr;
   564 			}
   565 cont_loop:
   566 			in_ptr++;
   567 		}
   568 		*out_ptr = '\0';
   569 		pPhrase->translations++;
   570 	}
   571 
   572 	return SMCParse_Continue;
   573 }
   574 
   575 SMCParseResult CPhraseFile::ReadSMC_LeavingSection()
   576 {
   577 	if (m_ParseState == PPS_InPhrase)
   578 	{
   579 		if (m_CurPhrase == -1 && m_LastPhraseString.size())
   580 		{
   581 			sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
   582 		}
   583 		m_CurPhrase = -1;
   584 		m_ParseState = PPS_Phrases;
   585 		m_LastPhraseString.assign("");
   586 	} else if (m_ParseState == PPS_Phrases) {
   587 		m_ParseState = PPS_None;
   588 	}
   589 
   590 	return SMCParse_Continue;
   591 }
   592 
   593 void CPhraseFile::ReadSMC_ParseEnd(bool halted, bool failed)
   594 {
   595 	/* Check to see if we have any dangling phrases that weren't completed, and scrap them */
   596 	if ((halted || failed) && m_LastPhraseString.size())
   597 	{
   598 		sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
   599 	}
   600 }
   601 
   602 TransError CPhraseFile::GetTranslation(const char *szPhrase, unsigned int lang_id, Translation *pTrans)
   603 {
   604 	if (lang_id >= m_LangCount)
   605 	{
   606 		return Trans_BadLanguage;
   607 	}
   608 
   609 	void *object;
   610 	if (!sm_trie_retrieve(m_pPhraseLookup, szPhrase, &object))
   611 	{
   612 		return Trans_BadPhrase;
   613 	}
   614 
   615 	phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(reinterpret_cast<int>(object));
   616 	trans_t *trans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
   617 
   618 	trans = &trans[lang_id];
   619 
   620 	if (trans->stridx == -1)
   621 	{
   622 		return Trans_BadPhraseLanguage;
   623 	}
   624 
   625 	pTrans->fmt_order = (int *)m_pMemory->GetAddress(trans->fmt_order);
   626 	pTrans->fmt_count = pPhrase->fmt_count;
   627 
   628 	pTrans->szPhrase = m_pStringTab->GetString(trans->stridx);
   629 
   630 	return Trans_Okay;
   631 }
   632 
   633 const char *CPhraseFile::GetFilename()
   634 {
   635 	return m_File.c_str();
   636 }
   637 
   638 /**************************
   639  ** MAIN TRANSLATOR CODE **
   640  **************************/
   641 
   642 Translator::Translator() : m_ServerLang(LANGUAGE_ENGLISH)
   643 {
   644 	m_pStringTab = new BaseStringTable(2048);
   645 	m_pLCodeLookup = sm_trie_create();
   646 	strncopy(m_InitialLang, "en", sizeof(m_InitialLang));
   647 }
   648 
   649 Translator::~Translator()
   650 {
   651 	for (size_t i=0; i<m_Files.size(); i++)
   652 	{
   653 		delete m_Files[i];
   654 	}
   655 
   656 	for (size_t i=0; i<m_Languages.size(); i++)
   657 	{
   658 		delete m_Languages[i];
   659 	}
   660 
   661 	sm_trie_destroy(m_pLCodeLookup);
   662 
   663 	delete m_pStringTab;
   664 }
   665 
   666 ConfigResult Translator::OnSourceModConfigChanged(const char *key, 
   667 									  const char *value, 
   668 									  ConfigSource source, 
   669 									  char *error, 
   670 									  size_t maxlength)
   671 {
   672 	if (strcasecmp(key, "ServerLang") == 0)
   673 	{
   674 		if (source == ConfigSource_Console)
   675 		{
   676 			unsigned int index;
   677 			if (!GetLanguageByCode(value, &index))
   678 			{
   679 				UTIL_Format(error, maxlength, "Language code \"%s\" is not registered", value);
   680 				return ConfigResult_Reject;
   681 			}
   682 
   683 			m_ServerLang = index;
   684 		} else {
   685 			strncopy(m_InitialLang, value, sizeof(m_InitialLang));
   686 		}
   687 
   688 		return ConfigResult_Accept;
   689 	}
   690 
   691 	return ConfigResult_Ignore;
   692 }
   693 
   694 void Translator::OnSourceModLevelChange(const char *mapName)
   695 {
   696 	/* Refresh language stuff */
   697 	char path[PLATFORM_MAX_PATH];
   698 	g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "configs/languages.cfg");
   699 	RebuildLanguageDatabase(path);
   700 }
   701 
   702 void Translator::OnSourceModAllInitialized()
   703 {
   704 	AddLanguage("en", "English");
   705 
   706 	unsigned int id;
   707 
   708 	//backwards compatibility shim
   709 	/* hack -- if core.cfg exists, exec it instead. */
   710 	char path[PLATFORM_MAX_PATH];
   711 	g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "translations/core.cfg");
   712 	
   713 	if (g_LibSys.PathExists(path))
   714 	{
   715 		id = FindOrAddPhraseFile("core.cfg");
   716 	} else {
   717 		id = FindOrAddPhraseFile("core.phrases.txt");
   718 	}
   719 
   720 	g_pCorePhrases = GetFileByIndex(id);
   721 }
   722 
   723 bool Translator::GetLanguageByCode(const char *code, unsigned int *index)
   724 {
   725 	void *_index;
   726 
   727 	if (!sm_trie_retrieve(m_pLCodeLookup, code, &_index))
   728 	{
   729 		return false;
   730 	}
   731 
   732 	if (index)
   733 	{
   734 		*index = reinterpret_cast<unsigned int>(_index);
   735 	}
   736 
   737 	return true;
   738 }
   739 
   740 bool Translator::GetLanguageByName(const char *name, unsigned int *index)
   741 {
   742 	CVector<Language *>::iterator iter;
   743 	unsigned int id = 0;
   744 
   745 	for (iter=m_Languages.begin(); iter!=m_Languages.end(); iter++, id++)
   746 	{
   747 		if (strcasecmp(m_pStringTab->GetString((*iter)->m_FullName), name) == 0)
   748 		{
   749 			break;
   750 		}
   751 	}
   752 
   753 	if (iter == m_Languages.end())
   754 	{
   755 		return false;
   756 	}
   757 
   758 	if (index)
   759 	{
   760 		*index = id;
   761 	}
   762 
   763 	return true;
   764 }
   765 
   766 unsigned int Translator::GetLanguageCount()
   767 {
   768 	return (unsigned int)m_Languages.size();
   769 }
   770 
   771 BaseStringTable *Translator::GetStringTable()
   772 {
   773 	return m_pStringTab;
   774 }
   775 
   776 unsigned int Translator::FindOrAddPhraseFile(const char *phrase_file)
   777 {
   778 	for (size_t i=0; i<m_Files.size(); i++)
   779 	{
   780 		if (strcmp(m_Files[i]->GetFilename(), phrase_file) == 0)
   781 		{
   782 			return (unsigned int)i;
   783 		}
   784 	}
   785 
   786 	CPhraseFile *pFile = new CPhraseFile(this, phrase_file);
   787 	unsigned int idx = (unsigned int)m_Files.size();
   788 	
   789 	m_Files.push_back(pFile);
   790 
   791 	pFile->ReparseFile();
   792 
   793 	return idx;
   794 }
   795 
   796 void Translator::RebuildLanguageDatabase(const char *lang_header_file)
   797 {
   798 	/* Erase everything we have */
   799 	sm_trie_destroy(m_pLCodeLookup);
   800 	m_pLCodeLookup = sm_trie_create();
   801 	m_pStringTab->Reset();
   802 
   803 	for (size_t i=0; i<m_Languages.size(); i++)
   804 	{
   805 		delete m_Languages[i];
   806 	}
   807 	m_Languages.clear();
   808 
   809 	/* Start anew */
   810 	SMCParseError err;
   811 	unsigned int line=0, col=0;
   812 	if ((err=g_TextParser.ParseFile_SMC(lang_header_file, this, &line, &col)) != SMCParse_Okay)
   813 	{
   814 		const char *str_err = g_TextParser.GetSMCErrorString(err);
   815 		if (!str_err)
   816 		{
   817 			str_err = m_CustomError.c_str();
   818 		}
   819 
   820 		g_Logger.LogError("[SM] Failed to parse language header file: \"%s\"", lang_header_file);
   821 		g_Logger.LogError("[SM] Parse error (line %d, column %d): %s", line, col, str_err);
   822 	}
   823 
   824 	void *serverLang;
   825 
   826 	if (!sm_trie_retrieve(m_pLCodeLookup, m_InitialLang, &serverLang))
   827 	{
   828 		g_Logger.LogError("Server language was set to bad language \"%s\" -- reverting to English", m_InitialLang);
   829 
   830 		strncopy(m_InitialLang, "en", sizeof(m_InitialLang));
   831 		m_ServerLang = LANGUAGE_ENGLISH;
   832 	}
   833 
   834 	m_ServerLang = reinterpret_cast<unsigned int>(serverLang);
   835 
   836 	if (!m_Languages.size())
   837 	{
   838 		g_Logger.LogError("[SM] Fatal error, no languages found! Translation will not work.");
   839 	}
   840 
   841 	for (size_t i=0; i<m_Files.size(); i++)
   842 	{
   843 		m_Files[i]->ReparseFile();
   844 	}
   845 }
   846 
   847 void Translator::ReadSMC_ParseStart()
   848 {
   849 	m_InLanguageSection = false;
   850 	m_CustomError.clear();
   851 }
   852 
   853 SMCParseResult Translator::ReadSMC_NewSection(const char *name, bool opt_quotes)
   854 {
   855 	if (!m_InLanguageSection)
   856 	{
   857 		if (strcmp(name, "Languages") == 0)
   858 		{
   859 			m_InLanguageSection = true;
   860 		}
   861 	}
   862 
   863 	if (!m_InLanguageSection)
   864 	{
   865 		g_Logger.LogError("[SM] Warning: Unrecognized section \"%s\" in languages.cfg", name);
   866 	}
   867 
   868 	return SMCParse_Continue;
   869 }
   870 
   871 SMCParseResult Translator::ReadSMC_LeavingSection()
   872 {
   873 	m_InLanguageSection = false;
   874 
   875 	return SMCParse_Continue;
   876 }
   877 
   878 SMCParseResult Translator::ReadSMC_KeyValue(const char *key, const char *value, bool key_quotes, bool value_quotes)
   879 {
   880 	size_t len = strlen(key);
   881 
   882 	if (len != 2)
   883 	{
   884 		g_Logger.LogError("[SM] Warning encountered parsing languages.cfg file.");
   885 		g_Logger.LogError("[SM] Invalid language code \"%s\" is being ignored.", key);
   886 	}
   887 
   888 	AddLanguage(key, value);
   889 
   890 	return SMCParse_Continue;
   891 }
   892 
   893 bool Translator::AddLanguage(const char *langcode, const char *description)
   894 {
   895 	if (sm_trie_retrieve(m_pLCodeLookup, langcode, NULL))
   896 	{
   897 		return false;
   898 	}
   899 
   900 	Language *pLanguage = new Language;
   901 	unsigned int idx = m_Languages.size();
   902 
   903 	pLanguage->m_code2[0] = langcode[0];
   904 	pLanguage->m_code2[1] = langcode[1];
   905 	pLanguage->m_code2[2] = langcode[2];
   906 	pLanguage->m_FullName = m_pStringTab->AddString(description);
   907 
   908 	sm_trie_insert(m_pLCodeLookup, langcode, reinterpret_cast<void *>(idx));
   909 
   910 	m_Languages.push_back(pLanguage);
   911 
   912 	return true;
   913 }
   914 
   915 CPhraseFile *Translator::GetFileByIndex(unsigned int index)
   916 {
   917 	if (index >= m_Files.size())
   918 	{
   919 		return NULL;
   920 	}
   921 
   922 	return m_Files[index];
   923 }
   924 
   925 size_t Translator::Translate(char *buffer, size_t maxlength, void **params, const Translation *pTrans)
   926 {
   927 	void *new_params[MAX_TRANSLATE_PARAMS];
   928 
   929 	/* Rewrite the parameter order */
   930 	for (unsigned int i=0; i<pTrans->fmt_count; i++)
   931 	{
   932 		new_params[i] = params[pTrans->fmt_order[i]];
   933 	}
   934 
   935 	return gnprintf(buffer, maxlength, pTrans->szPhrase, new_params);
   936 }
   937 
   938 TransError Translator::CoreTrans(int client, 
   939 								  char *buffer, 
   940 								  size_t maxlength, 
   941 								  const char *phrase, 
   942 								  void **params, 
   943 								  size_t *outlen)
   944 {
   945 	/* :TODO: do language stuff here */
   946 
   947 	if (!g_pCorePhrases)
   948 	{
   949 		return Trans_BadPhraseFile;
   950 	}
   951 
   952 	Translation trans;
   953 	TransError err;
   954 	
   955 	/* Using server lang temporarily until client lang stuff is implemented */
   956 	if ((err=g_pCorePhrases->GetTranslation(phrase, m_ServerLang, &trans)) != Trans_Okay)
   957 	{
   958 		return err;
   959 	}
   960 
   961 	size_t len = Translate(buffer, maxlength, params, &trans);
   962 
   963 	if (outlen)
   964 	{
   965 		*outlen = len;
   966 	}
   967 
   968 	return Trans_Okay;
   969 }
   970 
   971 unsigned int Translator::GetServerLanguage()
   972 {
   973 	return m_ServerLang;
   974 }
   975 
   976 unsigned int Translator::GetClientLanguage(int client)
   977 {
   978 	CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
   979 	return pPlayer->GetLanguageId();
   980 }
   981 
   982 bool Translator::GetLanguageInfo(unsigned int number, const char **code, const char **name)
   983 {
   984 	if (number >= GetLanguageCount())
   985 	{
   986 		return false;
   987 	}
   988 
   989 	Language *l = m_Languages[number];
   990 	if (code)
   991 	{
   992 		*code = l->m_code2;
   993 	}
   994 	if (name)
   995 	{
   996 		*name = m_pStringTab->GetString(l->m_FullName);
   997 	}
   998 
   999 	return true;
  1000 }