game_text.cpp

Go to the documentation of this file.
00001 /* $Id: game_text.cpp 25986 2013-11-13 21:49:31Z rubidium $ */
00002 
00003 /*
00004  * This file is part of OpenTTD.
00005  * OpenTTD 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, version 2.
00006  * OpenTTD 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.
00007  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
00008  */
00009 
00012 #include "../stdafx.h"
00013 #include "../strgen/strgen.h"
00014 #include "../debug.h"
00015 #include "../fileio_func.h"
00016 #include "../tar_type.h"
00017 #include "../script/squirrel_class.hpp"
00018 #include "../strings_func.h"
00019 #include "game_text.hpp"
00020 #include "game.hpp"
00021 #include "game_info.hpp"
00022 
00023 #include "table/strings.h"
00024 
00025 #include <stdarg.h>
00026 
00027 void CDECL strgen_warning(const char *s, ...)
00028 {
00029   char buf[1024];
00030   va_list va;
00031   va_start(va, s);
00032   vsnprintf(buf, lengthof(buf), s, va);
00033   va_end(va);
00034   DEBUG(script, 0, "%s:%d: warning: %s", _file, _cur_line, buf);
00035   _warnings++;
00036 }
00037 
00038 void CDECL strgen_error(const char *s, ...)
00039 {
00040   char buf[1024];
00041   va_list va;
00042   va_start(va, s);
00043   vsnprintf(buf, lengthof(buf), s, va);
00044   va_end(va);
00045   DEBUG(script, 0, "%s:%d: error: %s", _file, _cur_line, buf);
00046   _errors++;
00047 }
00048 
00049 void NORETURN CDECL strgen_fatal(const char *s, ...)
00050 {
00051   char buf[1024];
00052   va_list va;
00053   va_start(va, s);
00054   vsnprintf(buf, lengthof(buf), s, va);
00055   va_end(va);
00056   DEBUG(script, 0, "%s:%d: FATAL: %s", _file, _cur_line, buf);
00057   throw std::exception();
00058 }
00059 
00065 LanguageStrings::LanguageStrings(const char *language, const char *end)
00066 {
00067   this->language = end == NULL ? strdup(language) : strndup(language, end - language);
00068 }
00069 
00071 LanguageStrings::~LanguageStrings()
00072 {
00073   free(this->language);
00074 }
00075 
00081 LanguageStrings *ReadRawLanguageStrings(const char *file)
00082 {
00083   LanguageStrings *ret = NULL;
00084   try {
00085     size_t to_read;
00086     FILE *fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
00087     if (fh == NULL) {
00088       return NULL;
00089     }
00090 
00091     const char *langname = strrchr(file, PATHSEPCHAR);
00092     if (langname == NULL) {
00093       langname = file;
00094     } else {
00095       langname++;
00096     }
00097 
00098     /* Check for invalid empty filename */
00099     if (*langname == '.' || *langname == 0) return NULL;
00100 
00101     ret = new LanguageStrings(langname, strchr(langname, '.'));
00102 
00103     char buffer[2048];
00104     while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != NULL) {
00105       size_t len = strlen(buffer);
00106 
00107       /* Remove trailing spaces/newlines from the string. */
00108       size_t i = len;
00109       while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
00110       buffer[i] = '\0';
00111 
00112       *ret->lines.Append() = strndup(buffer, to_read);
00113 
00114       if (len > to_read) {
00115         to_read = 0;
00116       } else {
00117         to_read -= len;
00118       }
00119     }
00120 
00121     return ret;
00122   } catch (...) {
00123     delete ret;
00124     return NULL;
00125   }
00126 }
00127 
00128 
00130 struct StringListReader : StringReader {
00131   const char * const *p;   
00132   const char * const *end; 
00133 
00141   StringListReader(StringData &data, const LanguageStrings *strings, bool master, bool translation) :
00142       StringReader(data, strings->language, master, translation), p(strings->lines.Begin()), end(strings->lines.End())
00143   {
00144   }
00145 
00146   /* virtual */ char *ReadLine(char *buffer, size_t size)
00147   {
00148     if (this->p == this->end) return NULL;
00149 
00150     strncpy(buffer, *this->p, size);
00151     this->p++;
00152 
00153     return buffer;
00154   }
00155 };
00156 
00158 struct TranslationWriter : LanguageWriter {
00159   StringList *strings; 
00160 
00165   TranslationWriter(StringList *strings) : strings(strings)
00166   {
00167   }
00168 
00169   void WriteHeader(const LanguagePackHeader *header)
00170   {
00171     /* We don't use the header. */
00172   }
00173 
00174   void Finalise()
00175   {
00176     /* Nothing to do. */
00177   }
00178 
00179   void WriteLength(uint length)
00180   {
00181     /* We don't write the length. */
00182   }
00183 
00184   void Write(const byte *buffer, size_t length)
00185   {
00186     char *dest = MallocT<char>(length + 1);
00187     memcpy(dest, buffer, length);
00188     dest[length] = '\0';
00189     *this->strings->Append() = dest;
00190   }
00191 };
00192 
00194 struct StringNameWriter : HeaderWriter {
00195   StringList *strings; 
00196 
00201   StringNameWriter(StringList *strings) : strings(strings)
00202   {
00203   }
00204 
00205   void WriteStringID(const char *name, int stringid)
00206   {
00207     if (stringid == (int)this->strings->Length()) *this->strings->Append() = strdup(name);
00208   }
00209 
00210   void Finalise(const StringData &data)
00211   {
00212     /* Nothing to do. */
00213   }
00214 };
00215 
00219 class LanguageScanner : protected FileScanner {
00220 private:
00221   GameStrings *gs;
00222   char *exclude;
00223 
00224 public:
00226   LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(strdup(exclude)) {}
00227   ~LanguageScanner() { free(exclude); }
00228 
00232   void Scan(const char *directory)
00233   {
00234     this->FileScanner::Scan(".txt", directory, false);
00235   }
00236 
00237   /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
00238   {
00239     if (strcmp(filename, exclude) == 0) return true;
00240 
00241     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
00242     return true;
00243   }
00244 };
00245 
00250 GameStrings *LoadTranslations()
00251 {
00252   const GameInfo *info = Game::GetInfo();
00253   char filename[512];
00254   strecpy(filename, info->GetMainScript(), lastof(filename));
00255   char *e = strrchr(filename, PATHSEPCHAR);
00256   if (e == NULL) return NULL;
00257   e++; // Make 'e' point after the PATHSEPCHAR
00258 
00259   strecpy(e, "lang" PATHSEP "english.txt", lastof(filename));
00260   if (!FioCheckFileExists(filename, GAME_DIR)) return NULL;
00261 
00262   GameStrings *gs = new GameStrings();
00263   try {
00264     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
00265 
00266     /* Scan for other language files */
00267     LanguageScanner scanner(gs, filename);
00268     strecpy(e, "lang" PATHSEP, lastof(filename));
00269     size_t len = strlen(filename);
00270 
00271     const char *tar_filename = info->GetTarFile();
00272     TarList::iterator iter;
00273     if (tar_filename != NULL && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
00274       /* The main script is in a tar file, so find all files that
00275        * are in the same tar and add them to the langfile scanner. */
00276       TarFileList::iterator tar;
00277       FOR_ALL_TARS(tar, GAME_DIR) {
00278         /* Not in the same tar. */
00279         if (tar->second.tar_filename != iter->first) continue;
00280 
00281         /* Check the path and extension. */
00282         if (tar->first.size() <= len || tar->first.compare(0, len, filename) != 0) continue;
00283         if (tar->first.compare(tar->first.size() - 4, 4, ".txt") != 0) continue;
00284 
00285         scanner.AddFile(tar->first.c_str(), 0, tar_filename);
00286       }
00287     } else {
00288       /* Scan filesystem */
00289       scanner.Scan(filename);
00290     }
00291 
00292     gs->Compile();
00293     return gs;
00294   } catch (...) {
00295     delete gs;
00296     return NULL;
00297   }
00298 }
00299 
00301 void GameStrings::Compile()
00302 {
00303   StringData data(1);
00304   StringListReader master_reader(data, this->raw_strings[0], true, false);
00305   master_reader.ParseFile();
00306   if (_errors != 0) throw std::exception();
00307 
00308   this->version = data.Version();
00309 
00310   StringNameWriter id_writer(&this->string_names);
00311   id_writer.WriteHeader(data);
00312 
00313   for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
00314     data.FreeTranslation();
00315     StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
00316     translation_reader.ParseFile();
00317     if (_errors != 0) throw std::exception();
00318 
00319     LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
00320     TranslationWriter writer(&compiled->lines);
00321     writer.WriteLang(data);
00322   }
00323 }
00324 
00326 GameStrings *_current_data = NULL;
00327 
00333 const char *GetGameStringPtr(uint id)
00334 {
00335   if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
00336   return _current_data->cur_language->lines[id];
00337 }
00338 
00343 void RegisterGameTranslation(Squirrel *engine)
00344 {
00345   delete _current_data;
00346   _current_data = LoadTranslations();
00347   if (_current_data == NULL) return;
00348 
00349   HSQUIRRELVM vm = engine->GetVM();
00350   sq_pushroottable(vm);
00351   sq_pushstring(vm, _SC("GSText"), -1);
00352   if (SQ_FAILED(sq_get(vm, -2))) return;
00353 
00354   int idx = 0;
00355   for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
00356     sq_pushstring(vm, OTTD2SQ(*p), -1);
00357     sq_pushinteger(vm, idx);
00358     sq_rawset(vm, -3);
00359   }
00360 
00361   sq_pop(vm, 2);
00362 
00363   ReconsiderGameScriptLanguage();
00364 }
00365 
00369 void ReconsiderGameScriptLanguage()
00370 {
00371   if (_current_data == NULL) return;
00372 
00373   char temp[MAX_PATH];
00374   strecpy(temp, _current_language->file, temp + sizeof(temp));
00375 
00376   /* Remove the extension */
00377   char *l = strrchr(temp, '.');
00378   assert(l != NULL);
00379   *l = '\0';
00380 
00381   /* Skip the path */
00382   char *language = strrchr(temp, PATHSEPCHAR);
00383   assert(language != NULL);
00384   language++;
00385 
00386   for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
00387     if (strcmp((*p)->language, language) == 0) {
00388       _current_data->cur_language = *p;
00389       return;
00390     }
00391   }
00392 
00393   _current_data->cur_language = _current_data->compiled_strings[0];
00394 }