// vim: set ts=4 sw=4 tw=99 noet: // // AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). // Copyright (C) The AMX Mod X Development Team. // // This software is licensed under the GNU General Public License, version 3 or higher. // Additional exceptions apply. For full license details, see LICENSE.txt or visit: // https://alliedmods.net/amxmodx-license #include #include "amxmodx.h" #include "CLang.h" #include "format.h" #include "ITextParsers.h" #define LITIDX_NONE 0 #define LITIDX_BRACKET 1 #define LITIDX_DEFINITION 2 #define INSERT_NUMBER 1 #define INSERT_FLOAT 2 #define INSERT_STRING 3 #define INSERT_NEWLINE 4 template<> int Compare(const ke::AString &k1, const ke::AString &k2) { return k1.compare(k2); } template<> int CompareAlt(char const * const &k1, ke::AString const &k2) { return k2.compare(k1); } template<> int CompareAlt(ke::AString const &k1, ke::AString const &k2) { return k1.compare(k2); } template<> int HashFunction(const ke::AString &k) { unsigned long hash = 5381; register const char *str = k.chars(); register char c; while ((c = *str++)) { hash = ((hash << 5) + hash) + c; // hash*33 + c } return hash; } template<> int HashAlt(char const * const &k) { unsigned long hash = 5381; register const char *str = k; register char c; while ((c = *str++)) { hash = ((hash << 5) + hash) + c; // hash*33 + c } return hash; } template<> int HashAlt(ke::AString const &k) { unsigned long hash = 5381; register const char *str = k.chars(); register char c; while ((c = *str++)) { hash = ((hash << 5) + hash) + c; // hash*33 + c } return hash; } template<> int HashFunction(const int &k) { return k; } template<> int Compare(const int &k1, const int &k2) { return (k1 - k2); } // strip the whitespaces at the beginning and the end of a string // also convert to lowercase if needed // return the number of written characters (including the terimating zero char) size_t CLangMngr::strip(char *str, char *newstr, bool makelower) { size_t i = 0; size_t pos = 0; int flag = 0; size_t strln = strlen(str); for (i = strln - 1; i < strln; i--) { if (str[i] == '\n' || str[i] == ' ' || str[i] == '\t') { str[i] = 0; } else { break; } } char *ptr = str; while (*ptr) { if (!flag) { if (*ptr != '\n' && *ptr != ' ' && *ptr != '\t') { flag = 1; newstr[pos++] = makelower ? tolower(*ptr) : *ptr; } } else { newstr[pos++] = makelower ? tolower(*ptr) : *ptr; } ++ptr; } newstr[pos] = 0; return ptr - str + 1; } /******** CLangMngr::CLang *********/ CLangMngr::CLang::CLang() { m_LookUpTable.clear(); m_entries = 0; } CLangMngr::CLang::CLang(const char *lang) { m_LookUpTable.clear(); m_entries = 0; strncpy(m_LanguageName, lang, 2); m_LanguageName[2] = 0; } void CLangMngr::CLang::AddEntry(int key, const char *definition) { defentry &d = m_LookUpTable[key]; if (d.definition) { delete d.definition; } else { m_entries++; } d.definition = new ke::AString(definition); } CLangMngr::CLang::~CLang() { Clear(); } void CLangMngr::CLang::Clear() { THash::iterator iter; for (iter=m_LookUpTable.begin(); iter!=m_LookUpTable.end(); iter++) { if (iter->val.definition) { delete iter->val.definition; iter->val.definition = NULL; } } m_LookUpTable.clear(); m_entries = 0; } void CLangMngr::CLang::MergeDefinitions(ke::Vector &vec) { ke::AutoString *pDef; int key = -1; while (!vec.empty()) { auto keydef = vec.popCopy(); key = keydef.key; pDef = keydef.definition; AddEntry(key, pDef->ptr()); delete pDef; } } const char * CLangMngr::CLang::GetDef(int key, int &status) { defentry &def = m_LookUpTable[key]; if (!def.definition) { status = ERR_BADKEY; return NULL; } status = 0; return def.definition->chars(); } int CLangMngr::CLang::Entries() { return m_entries; } /******** CLangMngr *********/ CLangMngr::CLangMngr() { Clear(); } const char * CLangMngr::GetKey(int key) { if (key < 0 || key >= (int)KeyList.length()) return NULL; return KeyList[key]->chars(); } int CLangMngr::GetKeyEntry(const char *key) { keytbl_val &val = KeyTable[ke::AString(key)]; return val.index; } int CLangMngr::AddKeyEntry(const char *key) { keytbl_val val; val.index = static_cast(KeyList.length()); KeyList.append(new ke::AString(key)); KeyTable[ke::AString(key)] = val; return val.index; } int CLangMngr::AddKeyEntry(ke::AString &key) { return AddKeyEntry(key.chars()); } int CLangMngr::GetKeyEntry(ke::AString &key) { keytbl_val &val = KeyTable[key]; return val.index; } char * CLangMngr::FormatAmxString(AMX *amx, cell *params, int parm, int &len) { //do an initial run through all this static char outbuf[4096]; cell *addr = get_amxaddr(amx, params[parm++]); len = atcprintf(outbuf, sizeof(outbuf)-1, addr, amx, params, &parm); return outbuf; } void CLangMngr::MergeDefinitions(const char *lang, ke::Vector &tmpVec) { CLang * language = GetLang(lang); if (language) language->MergeDefinitions(tmpVec); } void reparse_newlines_and_color(char* def) { size_t len = strlen(def); int offs = 0; int c; if (!len) return; for (size_t i = 0; i < len; i++) { c = def[i]; if (c == '^' && (i != len - 1)) { c = def[++i]; if (c == 'n' || c == 't' || (c >= '1' && c <= '4')) { switch (c) { case '1': c = '\x01'; break; case '2': c = '\x02'; break; case '3': c = '\x03'; break; case '4': c = '\x04'; break; case 'n': c = '\n'; break; case 't': c = '\t'; break; } if (!g_bmod_cstrike && (c >= '1' && c <= '4')) // remove completely these two characters if not under CS { offs += 2; continue; } offs++; } } def[i - offs] = c; } def[len-offs] = '\0'; } struct LangFileData { void reset() { multiLine = false; *language = '\0'; *valueBuffer = '\0'; clearEntry(); } void clearEntry() { entry.key = -1; entry.definition = nullptr; } bool multiLine; char language[3]; char valueBuffer[512]; ke::AString currentFile; ke::AString lastKey; ke::Vector defsQueue; sKeyDef entry; } Data; void CLangMngr::ReadINI_ParseStart() { Data.reset(); } bool CLangMngr::ReadINI_NewSection(const char *section, bool invalid_tokens, bool close_bracket, bool extra_tokens, unsigned int *curtok) { if (Data.multiLine) { AMXXLOG_Log("New section, unterminated block (file \"%s\" key \"%s\" lang \"%s\")", Data.currentFile.chars(), Data.lastKey.chars(), Data.language); Data.clearEntry(); } if (!Data.defsQueue.empty()) { MergeDefinitions(Data.language, Data.defsQueue); } Data.reset(); Data.language[0] = section[0]; Data.language[1] = section[1]; Data.language[2] = '\0'; return true; } bool CLangMngr::ReadINI_KeyValue(const char *key, const char *value, bool invalid_tokens, bool equal_token, bool quotes, unsigned int *curtok) { bool colons_token = (key[strlen(key) - 1] == ':'); if (!Data.multiLine) { Data.lastKey = key; if (colons_token || equal_token) { int iKey = GetKeyEntry(key); if (iKey == -1) { iKey = AddKeyEntry(key); } if (equal_token) { if(value == nullptr) // Support empty value Data.valueBuffer[0] = '\0'; else strncopy(Data.valueBuffer, value, sizeof(Data.valueBuffer)); reparse_newlines_and_color(Data.valueBuffer); Data.entry.key = iKey; Data.entry.definition = new ke::AutoString; *Data.entry.definition = Data.valueBuffer; Data.defsQueue.append(Data.entry); Data.clearEntry(); } else if (!value && colons_token) { Data.entry.key = iKey; Data.entry.definition = new ke::AutoString; Data.multiLine = true; } } else { AMXXLOG_Log("Invalid multi-lingual line (file \"%s\" key \"%s\" lang \"%s\")", Data.currentFile.chars(), Data.lastKey.chars(), Data.language); } } else { if (!value && colons_token) { strncopy(Data.valueBuffer, Data.entry.definition->ptr(), sizeof(Data.valueBuffer)); reparse_newlines_and_color(Data.valueBuffer); *Data.entry.definition = Data.valueBuffer; Data.defsQueue.append(Data.entry); Data.clearEntry(); Data.multiLine = false; } else { if (!Data.entry.definition) { Data.entry.definition = new ke::AutoString(); } *Data.entry.definition = *Data.entry.definition + key; } } return true; } void CLangMngr::ReadINI_ParseEnd(bool halted) { if (!Data.defsQueue.empty()) { MergeDefinitions(Data.language, Data.defsQueue); } } //this is the file parser for dictionary text files // -- BAILOPAN int CLangMngr::MergeDefinitionFile(const char *file) { /** Tries to open the file. */ struct stat fileStat; if (stat(file, &fileStat)) { FileList.remove(file); AMXXLOG_Log("[AMXX] Failed to open dictionary file: %s", file); return 0; } /** Checks if there is an existing entry with same time stamp. */ time_t timeStamp; if (FileList.retrieve(file, &timeStamp) && fileStat.st_mtime == timeStamp) { return -1; } /** If yes, it either means that the entry doesn't exist or the existing entry needs to be updated. */ FileList.replace(file, fileStat.st_mtime); Data.currentFile = file; unsigned int line, col; bool result = textparsers->ParseFile_INI(file, static_cast(this), &line, &col, false); if (!result) { AMXXLOG_Log("[AMXX] Failed to re-open dictionary file: %s", file); return 0; } return 1; } // Find a CLang by name, if not found, add it CLangMngr::CLang * CLangMngr::GetLang(const char *name) { for (size_t iter = 0; iter < m_Languages.length(); ++iter) { if (strcmp(m_Languages[iter]->GetName(), name) == 0) return m_Languages[iter]; } CLang *p = new CLang(name); p->SetMngr(this); m_Languages.append(p); return p; } // Find a CLang by name, if not found, return NULL CLangMngr::CLang * CLangMngr::GetLangR(const char *name) { for (size_t iter = 0; iter < m_Languages.length(); ++iter) { if (strcmp(m_Languages[iter]->GetName(), name) == 0) return m_Languages[iter]; } return nullptr; } const char *CLangMngr::GetDef(const char *langName, const char *key, int &status) { CLang *lang = GetLangR(langName); keytbl_val &val = KeyTable.AltFindOrInsert(ke::AString(key)); //KeyTable[make_string(key)]; if (lang == NULL) { status = ERR_BADLANG; return NULL; } else if (val.index == -1) { status = ERR_BADKEY; return NULL; } else { status = 0; return lang->GetDef(val.index, status); } } void CLangMngr::InvalidateCache() { FileList.clear(); } CLangMngr::~CLangMngr() { Clear(); } void CLangMngr::Clear() { unsigned int i = 0; KeyTable.clear(); for (i = 0; i < m_Languages.length(); i++) { if (m_Languages[i]) delete m_Languages[i]; } for (i = 0; i < KeyList.length(); i++) { if (KeyList[i]) delete KeyList[i]; } m_Languages.clear(); KeyList.clear(); FileList.clear(); } int CLangMngr::GetLangsNum() { return m_Languages.length(); } const char *CLangMngr::GetLangName(int langId) { for (size_t iter = 0; iter < m_Languages.length(); ++iter) { if ((int)iter == langId) { return m_Languages[iter]->GetName(); } } return ""; } bool CLangMngr::LangExists(const char *langName) { char buf[3] = {0}; int i = 0; while ((buf[i] = tolower(*langName++))) { if (++i == 2) break; } for (size_t iter = 0; iter < m_Languages.length(); ++iter) { if (strcmp(m_Languages[iter]->GetName(), buf) == 0) { return true; } } return false; } void CLangMngr::SetDefLang(int id) { m_CurGlobId = id; }