Removed pointless "systems" folder from core.
3 * =============================================================================
5 * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved.
6 * =============================================================================
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.
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
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/>.
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>.
35 #include "Translator.h"
37 #include "LibrarySys.h"
38 #include "sm_stringutil.h"
39 #include "sourcemod.h"
40 #include "PlayerManager.h"
41 #include "PhraseCollection.h"
44 Translator g_Translator;
45 IPhraseCollection *g_pCorePhrases = NULL;
46 unsigned int g_pCorePhraseID = 0;
57 unsigned int fmt_count;
58 unsigned int fmt_bytes;
60 unsigned int translations;
63 CPhraseFile::CPhraseFile(Translator *pTranslator, const char *file)
65 m_pStringTab = pTranslator->GetStringTable();
66 m_pMemory = m_pStringTab->GetMemTable();
67 m_LangCount = pTranslator->GetLanguageCount();
69 m_pTranslator = pTranslator;
70 m_pPhraseLookup = NULL;
73 CPhraseFile::~CPhraseFile()
77 sm_trie_destroy(m_pPhraseLookup);
81 void CPhraseFile::ParseError(const char *message, ...)
85 va_start(ap, message);
86 vsnprintf(buffer, sizeof(buffer), message, ap);
89 m_ParseError.assign(buffer);
92 void CPhraseFile::ParseWarning(const char *message, ...)
96 va_start(ap, message);
97 vsnprintf(buffer, sizeof(buffer), message, ap);
102 g_Logger.LogError("[SM] Warning(s) encountered in translation file \"%s\"", m_File.c_str());
106 g_Logger.LogError("[SM] %s", buffer);
109 void CPhraseFile::ReparseFile()
113 sm_trie_destroy(m_pPhraseLookup);
115 m_pPhraseLookup = sm_trie_create();
117 m_LangCount = m_pTranslator->GetLanguageCount();
125 char path[PLATFORM_MAX_PATH];
126 g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "translations/%s", m_File.c_str());
128 //backwards compatibility shim
129 /* :HACKHACK: Change .cfg/.txt and vice versa for compatibility */
130 if (!g_LibSys.PathExists(path))
132 if (m_File.compare("common.cfg") == 0)
134 UTIL_ReplaceAll(path, sizeof(path), "common.cfg", "common.phrases.txt");
135 } else if (strstr(path, ".cfg")) {
136 UTIL_ReplaceAll(path, sizeof(path), ".cfg", ".txt");
137 } else if (strstr(path, ".txt")) {
138 UTIL_ReplaceAll(path, sizeof(path), ".txt", ".cfg");
144 if ((err=textparsers->ParseFile_SMC(path, this, &states)) != SMCError_Okay)
146 const char *msg = textparsers->GetSMCErrorString(err);
149 msg = m_ParseError.c_str();
152 g_Logger.LogError("[SM] Fatal error encountered parsing translation file \"%s\"", m_File.c_str());
153 g_Logger.LogError("[SM] Error (line %d, column %d): %s", states.line, states.col, msg);
157 for (unsigned int i = 1; i < m_LangCount; i++)
159 if (!m_pTranslator->GetLanguageInfo(i, &code, NULL))
164 g_SourceMod.BuildPath(Path_SM,
167 "translations/%s/%s",
171 /* Speculatively load these. */
172 if (!g_LibSys.PathExists(path))
177 if ((err=textparsers->ParseFile_SMC(path, this, &states)) != SMCError_Okay)
179 const char *msg = textparsers->GetSMCErrorString(err);
182 msg = m_ParseError.c_str();
185 g_Logger.LogError("[SM] Fatal error encountered parsing translation file \"%s/%s\"",
188 g_Logger.LogError("[SM] Error (line %d, column %d): %s",
196 void CPhraseFile::ReadSMC_ParseStart()
199 m_ParseState = PPS_None;
200 m_FileLogged = false;
201 m_LastPhraseString.clear();
204 SMCResult CPhraseFile::ReadSMC_NewSection(const SMCStates *states, const char *name)
206 bool recognized = false;
207 if (m_ParseState == PPS_None)
209 if (strcmp(name, "Phrases") == 0)
211 m_ParseState = PPS_Phrases;
215 else if (m_ParseState == PPS_Phrases)
217 m_ParseState = PPS_InPhrase;
221 if (sm_trie_retrieve(m_pPhraseLookup, name, &value))
223 m_CurPhrase = reinterpret_cast<int>(value);
229 m_CurPhrase = m_pMemory->CreateMem(sizeof(phrase_t), (void **)&pPhrase);
230 sm_trie_insert(m_pPhraseLookup, name, reinterpret_cast<void *>(m_CurPhrase));
232 /* Initialize new phrase */
235 pPhrase->fmt_count = 0;
236 pPhrase->fmt_list = -1;
238 int trans_tbl = m_pMemory->CreateMem(sizeof(trans_t) * m_LangCount, (void **)&pTrans);
239 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
240 pPhrase->trans_tbl = trans_tbl;
242 pPhrase->translations = 0;
243 pPhrase->fmt_bytes = 0;
245 for (unsigned int i=0; i<m_LangCount; i++)
247 pTrans[i].stridx = -1;
251 m_LastPhraseString.assign(name);
253 else if (m_ParseState == PPS_InPhrase)
255 ParseError("Phrase sections may not have sub-sections");
256 return SMCResult_HaltFail;
261 ParseWarning("Ignoring invalid section \"%s\" on line %d.", name, states->line);
264 return SMCResult_Continue;
267 SMCResult CPhraseFile::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
269 /* See if we are ignoring a phrase */
270 if (m_CurPhrase == -1)
272 return SMCResult_Continue;
275 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
277 /* Duplicate format keys get silently ignored. */
279 && strcmp(key, "#format") == 0
280 && pPhrase->fmt_list == -1)
282 if (pPhrase->translations > 0)
284 ParseWarning("#format property should come before translations on line %d, ignoring", states->line);
285 return SMCResult_Continue;
288 /* Do an initial browsing for error checking and what not */
296 ParseStates state = Parse_None;
298 const char *old_value = value;
299 const char *last_value = value;
300 while (*value != '\0')
302 if (state == Parse_None)
306 pPhrase->fmt_count++;
309 else if (*value == ',')
315 unsigned int bytes = textparsers->GetUTF8CharBytes(value);
316 if (bytes != 1 || !isalpha(*value))
318 ParseWarning("Invalid token '%c' in #format property on line %d.", *value, states->line);
322 else if (state == Parse_Index)
326 state = Parse_Format;
327 if (value - last_value >= 15)
329 ParseWarning("Too many digits in format index on line %d, phrase will be ignored.", states->line);
331 return SMCResult_Continue;
336 unsigned int bytes = textparsers->GetUTF8CharBytes(value);
337 if (bytes != 1 || !isdigit(*value))
339 ParseWarning("Token '%c' in #format property on line %d is not a digit, phrase will be ignored.",
343 return SMCResult_Continue;
347 else if (state == Parse_Format)
352 last_value = value + 1;
358 if (state != Parse_None)
360 /* Moose clam cow. */
361 ParseWarning("Unterminated format string on line %d, phrase will be ignored.", states->line);
363 return SMCResult_Continue;
367 /* Allocate the format table */
370 int tmp = m_pMemory->CreateMem(sizeof(int) * pPhrase->fmt_count, (void **)&fmt_list);
372 /* Update the phrase pointer in case it changed */
373 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
374 pPhrase->fmt_list = tmp;
377 for (size_t i=0; i<pPhrase->fmt_count; i++)
382 /* Now we need to read again... this time into the format buffer */
383 const char *in_ptr = value;
384 const char *idx_ptr = NULL;
385 char *out_ptr = NULL;
386 unsigned int cur_idx = 0;
388 while (*in_ptr != '\0')
390 if (state == Parse_None)
398 else if (state == Parse_Index)
402 /* Check the number! */
405 ParseWarning("Format property contains unindexed format string on line %d, phrase will be ignored.", states->line);
407 return SMCResult_Continue;
409 long idx = strtol(idx_ptr, NULL, 10);
410 if (idx < 1 || idx > (long)pPhrase->fmt_count)
412 ParseWarning("Format property contains invalid index '%d' on line %d, phrase will be ignored.", idx, states->line);
414 return SMCResult_Continue;
416 else if (fmt_list[idx - 1] != -1)
418 ParseWarning("Format property contains duplicated index '%d' on line %d, phrase will be ignored.", idx, states->line);
420 return SMCResult_Continue;
422 cur_idx = (unsigned int)idx;
423 state = Parse_Format;
431 else if (state == Parse_Format)
437 ParseWarning("Format property contains empty format string on line %d, phrase will be ignored.", states->line);
439 return SMCResult_Continue;
443 /* Now, add this to our table */
444 int tmp_idx = m_pStringTab->AddString(fmt_buf);
445 /* Update pointers and update necessary variables */
446 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
447 pPhrase->fmt_bytes += strlen(fmt_buf);
448 fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
449 fmt_list[cur_idx - 1] = tmp_idx;
458 /* Check length ... */
459 if ((unsigned)(out_ptr - fmt_buf) >= sizeof(fmt_buf) - 1)
461 ParseWarning("Format property contains format string that exceeds maximum length on line %d, phrase will be ignored.",
464 return SMCResult_Continue;
466 *out_ptr++ = *in_ptr;
472 /* If we've gotten here, we only accepted unique indexes in a range.
473 * Therefore, the list has to be completely filled. Double check anyway.
475 for (size_t i=0; i<pPhrase->fmt_count; i++)
477 if (fmt_list[i] == -1)
479 ParseWarning("Format property contains no string for index %d on line %d, phrase will be ignored.",
483 return SMCResult_Continue;
489 size_t len = strlen(key);
492 ParseWarning("Ignoring translation to invalid language \"%s\" on line %d.", key, states->line);
493 return SMCResult_Continue;
497 if (!m_pTranslator->GetLanguageByCode(key, &lang))
499 /* Ignore if we don't have a language.
500 * :IDEA: issue a one-time alert?
502 return SMCResult_Continue;
505 /* See how many bytes we need for this string, then allocate.
506 * NOTE: THIS SHOULD GUARANTEE THAT WE DO NOT NEED TO NEED TO SIZE CHECK
508 len = strlen(value) + pPhrase->fmt_bytes + 1;
512 out_idx = m_pMemory->CreateMem(len, (void **)&out_buf);
515 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
518 int *fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
519 trans_t *pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
521 pTrans = &pTrans[lang];
522 pTrans->stridx = out_idx;
524 bool params[MAX_TRANSLATE_PARAMS];
526 /* Build the format order list, if necessary */
529 int tmp = m_pMemory->CreateMem(pPhrase->fmt_count * sizeof(int), (void **)&fmt_order);
531 /* Update pointers */
532 pPhrase = (phrase_t *)m_pMemory->GetAddress(m_CurPhrase);
533 pTrans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
534 fmt_list = (int *)m_pMemory->GetAddress(pPhrase->fmt_list);
535 out_buf = (char *)m_pMemory->GetAddress(out_idx);
536 pTrans = &pTrans[lang];
538 /* Now it's safe to save the index */
539 pTrans->fmt_order = tmp;
541 for (unsigned int i=0; i<pPhrase->fmt_count; i++)
545 memset(¶ms[0], 0, sizeof(params));
548 const char *in_ptr = value;
549 char *out_ptr = out_buf;
550 unsigned int order_idx = 0;
551 while (*in_ptr != '\0')
555 switch (*(in_ptr + 1))
570 /* Copy both bytes since we don't know what's going on */
571 *out_ptr++ = *in_ptr++;
572 *out_ptr++ = *in_ptr;
575 /* Skip past the last byte read */
578 else if (*in_ptr == '{' && fmt_list != NULL)
580 /* Search for parameters if this is a formatted string */
581 const char *scrap_in_point = in_ptr;
582 const char *digit_start = ++in_ptr;
584 while (*in_ptr != '\0')
586 bytes = textparsers->GetUTF8CharBytes(in_ptr);
593 /* Did we get an index? */
594 if (in_ptr == digit_start)
599 long idx = strtol(digit_start, NULL, 10);
600 if (idx < 1 || idx > (int)pPhrase->fmt_count)
609 /* We're safe to insert the string. First mark order. */
610 fmt_order[order_idx++] = (int)idx - 1;
611 /* Now concatenate */
612 out_ptr += sprintf(out_ptr, "%s", m_pStringTab->GetString(fmt_list[idx-1]));
614 params[idx-1] = true;
621 /* Pretend none of this ever happened. Move along now! */
622 in_ptr = scrap_in_point;
623 *out_ptr++ = *in_ptr;
625 *out_ptr++ = *in_ptr;
631 pPhrase->translations++;
634 return SMCResult_Continue;
637 SMCResult CPhraseFile::ReadSMC_LeavingSection(const SMCStates *states)
639 if (m_ParseState == PPS_InPhrase)
641 if (m_CurPhrase == -1 && m_LastPhraseString.size())
643 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
646 m_ParseState = PPS_Phrases;
647 m_LastPhraseString.assign("");
649 else if (m_ParseState == PPS_Phrases)
651 m_ParseState = PPS_None;
654 return SMCResult_Continue;
657 void CPhraseFile::ReadSMC_ParseEnd(bool halted, bool failed)
659 /* Check to see if we have any dangling phrases that weren't completed, and scrap them */
660 if ((halted || failed) && m_LastPhraseString.size())
662 sm_trie_delete(m_pPhraseLookup, m_LastPhraseString.c_str());
666 TransError CPhraseFile::GetTranslation(const char *szPhrase, unsigned int lang_id, Translation *pTrans)
668 if (lang_id >= m_LangCount)
670 return Trans_BadLanguage;
674 if (!sm_trie_retrieve(m_pPhraseLookup, szPhrase, &object))
676 return Trans_BadPhrase;
679 phrase_t *pPhrase = (phrase_t *)m_pMemory->GetAddress(reinterpret_cast<int>(object));
680 trans_t *trans = (trans_t *)m_pMemory->GetAddress(pPhrase->trans_tbl);
682 trans = &trans[lang_id];
684 if (trans->stridx == -1)
686 return Trans_BadPhraseLanguage;
689 pTrans->fmt_count = pPhrase->fmt_count;
690 pTrans->fmt_order = pTrans->fmt_count > 0 ? (int *)m_pMemory->GetAddress(trans->fmt_order) : NULL;
691 pTrans->szPhrase = m_pStringTab->GetString(trans->stridx);
696 const char *CPhraseFile::GetFilename()
698 return m_File.c_str();
701 /**************************
702 ** MAIN TRANSLATOR CODE **
703 **************************/
705 Translator::Translator() : m_ServerLang(SOURCEMOD_LANGUAGE_ENGLISH)
707 m_pStringTab = new BaseStringTable(2048);
708 m_pLCodeLookup = sm_trie_create();
709 strncopy(m_InitialLang, "en", sizeof(m_InitialLang));
712 Translator::~Translator()
714 for (size_t i=0; i<m_Files.size(); i++)
719 for (size_t i=0; i<m_Languages.size(); i++)
721 delete m_Languages[i];
724 sm_trie_destroy(m_pLCodeLookup);
729 ConfigResult Translator::OnSourceModConfigChanged(const char *key,
735 if (strcasecmp(key, "ServerLang") == 0)
737 if (source == ConfigSource_Console)
740 if (!GetLanguageByCode(value, &index))
742 UTIL_Format(error, maxlength, "Language code \"%s\" is not registered", value);
743 return ConfigResult_Reject;
746 m_ServerLang = index;
748 strncopy(m_InitialLang, value, sizeof(m_InitialLang));
751 return ConfigResult_Accept;
754 return ConfigResult_Ignore;
757 void Translator::OnSourceModLevelChange(const char *mapName)
759 /* Refresh language stuff */
760 char path[PLATFORM_MAX_PATH];
761 g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "configs/languages.cfg");
762 RebuildLanguageDatabase(path);
765 void Translator::OnSourceModAllInitialized()
767 AddLanguage("en", "English");
769 g_pCorePhrases = CreatePhraseCollection();
770 g_pCorePhrases->AddPhraseFile("core.phrases");
772 g_ShareSys.AddInterface(NULL, this);
775 void Translator::OnSourceModShutdown()
777 g_pCorePhrases->Destroy();
780 bool Translator::GetLanguageByCode(const char *code, unsigned int *index)
784 if (!sm_trie_retrieve(m_pLCodeLookup, code, &_index))
791 *index = reinterpret_cast<unsigned int>(_index);
797 bool Translator::GetLanguageByName(const char *name, unsigned int *index)
799 CVector<Language *>::iterator iter;
802 for (iter=m_Languages.begin(); iter!=m_Languages.end(); iter++, id++)
804 if (strcasecmp(m_pStringTab->GetString((*iter)->m_FullName), name) == 0)
810 if (iter == m_Languages.end())
823 unsigned int Translator::GetLanguageCount()
825 return (unsigned int)m_Languages.size();
828 BaseStringTable *Translator::GetStringTable()
833 unsigned int Translator::FindOrAddPhraseFile(const char *phrase_file)
835 for (size_t i=0; i<m_Files.size(); i++)
837 if (strcmp(m_Files[i]->GetFilename(), phrase_file) == 0)
839 return (unsigned int)i;
843 CPhraseFile *pFile = new CPhraseFile(this, phrase_file);
844 unsigned int idx = (unsigned int)m_Files.size();
846 m_Files.push_back(pFile);
848 pFile->ReparseFile();
853 void Translator::RebuildLanguageDatabase(const char *lang_header_file)
855 /* Erase everything we have */
856 sm_trie_destroy(m_pLCodeLookup);
857 m_pLCodeLookup = sm_trie_create();
858 m_pStringTab->Reset();
860 for (size_t i=0; i<m_Languages.size(); i++)
862 delete m_Languages[i];
869 if ((err=textparsers->ParseFile_SMC(lang_header_file, this, &states)) != SMCError_Okay)
871 const char *str_err = textparsers->GetSMCErrorString(err);
874 str_err = m_CustomError.c_str();
877 g_Logger.LogError("[SM] Failed to parse language header file: \"%s\"", lang_header_file);
878 g_Logger.LogError("[SM] Parse error (line %d, column %d): %s", states.line, states.col, str_err);
883 if (!sm_trie_retrieve(m_pLCodeLookup, m_InitialLang, &serverLang))
885 g_Logger.LogError("Server language was set to bad language \"%s\" -- reverting to English", m_InitialLang);
887 strncopy(m_InitialLang, "en", sizeof(m_InitialLang));
888 m_ServerLang = SOURCEMOD_LANGUAGE_ENGLISH;
891 m_ServerLang = reinterpret_cast<unsigned int>(serverLang);
893 if (!m_Languages.size())
895 g_Logger.LogError("[SM] Fatal error, no languages found! Translation will not work.");
898 for (size_t i=0; i<m_Files.size(); i++)
900 m_Files[i]->ReparseFile();
904 void Translator::ReadSMC_ParseStart()
906 m_InLanguageSection = false;
907 m_CustomError.clear();
910 SMCResult Translator::ReadSMC_NewSection(const SMCStates *states, const char *name)
912 if (!m_InLanguageSection)
914 if (strcmp(name, "Languages") == 0)
916 m_InLanguageSection = true;
920 if (!m_InLanguageSection)
922 g_Logger.LogError("[SM] Warning: Unrecognized section \"%s\" in languages.cfg", name);
925 return SMCResult_Continue;
928 SMCResult Translator::ReadSMC_LeavingSection(const SMCStates *states)
930 m_InLanguageSection = false;
932 return SMCResult_Continue;
935 SMCResult Translator::ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value)
937 size_t len = strlen(key);
941 g_Logger.LogError("[SM] Warning encountered parsing languages.cfg file.");
942 g_Logger.LogError("[SM] Invalid language code \"%s\" is being ignored.", key);
945 AddLanguage(key, value);
947 return SMCResult_Continue;
950 bool Translator::AddLanguage(const char *langcode, const char *description)
952 if (sm_trie_retrieve(m_pLCodeLookup, langcode, NULL))
957 Language *pLanguage = new Language;
958 unsigned int idx = m_Languages.size();
960 pLanguage->m_code2[0] = langcode[0];
961 pLanguage->m_code2[1] = langcode[1];
962 pLanguage->m_code2[2] = langcode[2];
963 pLanguage->m_FullName = m_pStringTab->AddString(description);
965 sm_trie_insert(m_pLCodeLookup, langcode, reinterpret_cast<void *>(idx));
967 m_Languages.push_back(pLanguage);
972 CPhraseFile *Translator::GetFileByIndex(unsigned int index)
974 if (index >= m_Files.size())
979 return m_Files[index];
982 unsigned int Translator::GetServerLanguage()
987 unsigned int Translator::GetClientLanguage(int client)
989 CPlayer *pPlayer = g_Players.GetPlayerByIndex(client);
990 return pPlayer->GetLanguageId();
993 bool Translator::GetLanguageInfo(unsigned int number, const char **code, const char **name)
995 if (number >= GetLanguageCount())
1000 Language *l = m_Languages[number];
1007 *name = m_pStringTab->GetString(l->m_FullName);
1013 const char *Translator::GetInterfaceName()
1015 return SMINTERFACE_TRANSLATOR_NAME;
1018 unsigned int Translator::GetInterfaceVersion()
1020 return SMINTERFACE_TRANSLATOR_VERSION;
1023 IPhraseCollection *Translator::CreatePhraseCollection()
1025 return new CPhraseCollection();
1028 int Translator::SetGlobalTarget(int index)
1030 return g_SourceMod.SetGlobalTarget(index);
1033 int Translator::GetGlobalTarget() const
1035 return g_SourceMod.GetGlobalTarget();
1038 bool CoreTranslate(char *buffer,
1041 unsigned int numparams,
1047 const char *fail_phrase;
1048 void *params[MAX_TRANSLATE_PARAMS];
1050 if (numparams > MAX_TRANSLATE_PARAMS)
1056 va_start(ap, pOutLength);
1057 for (i = 0; i < numparams; i++)
1059 params[i] = va_arg(ap, void *);
1063 if (!g_pCorePhrases->FormatString(buffer,
1071 if (fail_phrase != NULL)
1073 g_Logger.LogError("[SM] Could not find core phrase: %s", fail_phrase);
1077 g_Logger.LogError("[SM] Unknown fatal error while translating a core phrase.");
1086 bool Translator::FormatString(char *buffer,
1089 IPhraseCollection *pPhrases,
1091 unsigned int numparams,
1093 const char **pFailPhrase)
1098 if (!gnprintf(buffer, maxlength, format, pPhrases, params, numparams, arg, pOutLength, pFailPhrase))
1103 if (arg != numparams)
1105 if (pFailPhrase != NULL)
1107 *pFailPhrase = NULL;