# game_text.cpp

Go to the documentation of this file.

    00001 /* $Id: game_text.cpp 26543 2014-04-29 18:35:01Z frosch $ */
    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   FILE *fh = NULL;
    00085   try {
    00086     size_t to_read;
    00087     fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
    00088     if (fh == NULL) {
    00089       return NULL;
    00090     }
    00091
    00092     const char *langname = strrchr(file, PATHSEPCHAR);
    00093     if (langname == NULL) {
    00094       langname = file;
    00095     } else {
    00096       langname++;
    00097     }
    00098
    00099     /* Check for invalid empty filename */
    00100     if (*langname == '.' || *langname == 0) {
    00101       fclose(fh);
    00102       return NULL;
    00103     }
    00104
    00105     ret = new LanguageStrings(langname, strchr(langname, '.'));
    00106
    00107     char buffer[2048];
    00108     while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != NULL) {
    00109       size_t len = strlen(buffer);
    00110
    00111       /* Remove trailing spaces/newlines from the string. */
    00112       size_t i = len;
    00113       while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
    00114       buffer[i] = '\0';
    00115
    00116       *ret->lines.Append() = strndup(buffer, to_read);
    00117
    00118       if (len > to_read) {
    00119         to_read = 0;
    00120       } else {
    00121         to_read -= len;
    00122       }
    00123     }
    00124
    00125     fclose(fh);
    00126     return ret;
    00127   } catch (...) {
    00128     if (fh != NULL) fclose(fh);
    00129     delete ret;
    00130     return NULL;
    00131   }
    00132 }
    00133
    00134
    00136 struct StringListReader : StringReader {
    00137   const char * const *p;
    00138   const char * const *end;
    00139
    00147   StringListReader(StringData &data, const LanguageStrings *strings, bool master, bool translation) :
    00148       StringReader(data, strings->language, master, translation), p(strings->lines.Begin()), end(strings->lines.End())
    00149   {
    00150   }
    00151
    00152   /* virtual */ char *ReadLine(char *buffer, size_t size)
    00153   {
    00154     if (this->p == this->end) return NULL;
    00155
    00156     strncpy(buffer, *this->p, size);
    00157     this->p++;
    00158
    00159     return buffer;
    00160   }
    00161 };
    00162
    00164 struct TranslationWriter : LanguageWriter {
    00165   StringList *strings;
    00166
    00171   TranslationWriter(StringList *strings) : strings(strings)
    00172   {
    00173   }
    00174
    00175   void WriteHeader(const LanguagePackHeader *header)
    00176   {
    00177     /* We don't use the header. */
    00178   }
    00179
    00180   void Finalise()
    00181   {
    00182     /* Nothing to do. */
    00183   }
    00184
    00185   void WriteLength(uint length)
    00186   {
    00187     /* We don't write the length. */
    00188   }
    00189
    00190   void Write(const byte *buffer, size_t length)
    00191   {
    00192     char *dest = MallocT<char>(length + 1);
    00193     memcpy(dest, buffer, length);
    00194     dest[length] = '\0';
    00195     *this->strings->Append() = dest;
    00196   }
    00197 };
    00198
    00200 struct StringNameWriter : HeaderWriter {
    00201   StringList *strings;
    00202
    00207   StringNameWriter(StringList *strings) : strings(strings)
    00208   {
    00209   }
    00210
    00211   void WriteStringID(const char *name, int stringid)
    00212   {
    00213     if (stringid == (int)this->strings->Length()) *this->strings->Append() = strdup(name);
    00214   }
    00215
    00216   void Finalise(const StringData &data)
    00217   {
    00218     /* Nothing to do. */
    00219   }
    00220 };
    00221
    00225 class LanguageScanner : protected FileScanner {
    00226 private:
    00227   GameStrings *gs;
    00228   char *exclude;
    00229
    00230 public:
    00232   LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(strdup(exclude)) {}
    00233   ~LanguageScanner() { free(exclude); }
    00234
    00238   void Scan(const char *directory)
    00239   {
    00240     this->FileScanner::Scan(".txt", directory, false);
    00241   }
    00242
    00243   /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
    00244   {
    00245     if (strcmp(filename, exclude) == 0) return true;
    00246
    00247     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
    00248     return true;
    00249   }
    00250 };
    00251
    00256 GameStrings *LoadTranslations()
    00257 {
    00258   const GameInfo *info = Game::GetInfo();
    00259   char filename[512];
    00260   strecpy(filename, info->GetMainScript(), lastof(filename));
    00261   char *e = strrchr(filename, PATHSEPCHAR);
    00262   if (e == NULL) return NULL;
    00263   e++; // Make 'e' point after the PATHSEPCHAR
    00264
    00265   strecpy(e, "lang" PATHSEP "english.txt", lastof(filename));
    00266   if (!FioCheckFileExists(filename, GAME_DIR)) return NULL;
    00267
    00268   GameStrings *gs = new GameStrings();
    00269   try {
    00270     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
    00271
    00272     /* Scan for other language files */
    00273     LanguageScanner scanner(gs, filename);
    00274     strecpy(e, "lang" PATHSEP, lastof(filename));
    00275     size_t len = strlen(filename);
    00276
    00277     const char *tar_filename = info->GetTarFile();
    00278     TarList::iterator iter;
    00279     if (tar_filename != NULL && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
    00280       /* The main script is in a tar file, so find all files that
    00281        * are in the same tar and add them to the langfile scanner. */
    00282       TarFileList::iterator tar;
    00283       FOR_ALL_TARS(tar, GAME_DIR) {
    00284         /* Not in the same tar. */
    00285         if (tar->second.tar_filename != iter->first) continue;
    00286
    00287         /* Check the path and extension. */
    00288         if (tar->first.size() <= len || tar->first.compare(0, len, filename) != 0) continue;
    00289         if (tar->first.compare(tar->first.size() - 4, 4, ".txt") != 0) continue;
    00290
    00291         scanner.AddFile(tar->first.c_str(), 0, tar_filename);
    00292       }
    00293     } else {
    00294       /* Scan filesystem */
    00295       scanner.Scan(filename);
    00296     }
    00297
    00298     gs->Compile();
    00299     return gs;
    00300   } catch (...) {
    00301     delete gs;
    00302     return NULL;
    00303   }
    00304 }
    00305
    00307 void GameStrings::Compile()
    00308 {
    00309   StringData data(1);
    00310   StringListReader master_reader(data, this->raw_strings[0], true, false);
    00311   master_reader.ParseFile();
    00312   if (_errors != 0) throw std::exception();
    00313
    00314   this->version = data.Version();
    00315
    00316   StringNameWriter id_writer(&this->string_names);
    00317   id_writer.WriteHeader(data);
    00318
    00319   for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
    00320     data.FreeTranslation();
    00321     StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
    00322     translation_reader.ParseFile();
    00323     if (_errors != 0) throw std::exception();
    00324
    00325     LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
    00326     TranslationWriter writer(&compiled->lines);
    00327     writer.WriteLang(data);
    00328   }
    00329 }
    00330
    00332 GameStrings *_current_data = NULL;
    00333
    00339 const char *GetGameStringPtr(uint id)
    00340 {
    00341   if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
    00342   return _current_data->cur_language->lines[id];
    00343 }
    00344
    00349 void RegisterGameTranslation(Squirrel *engine)
    00350 {
    00351   delete _current_data;
    00352   _current_data = LoadTranslations();
    00353   if (_current_data == NULL) return;
    00354
    00355   HSQUIRRELVM vm = engine->GetVM();
    00356   sq_pushroottable(vm);
    00357   sq_pushstring(vm, _SC("GSText"), -1);
    00358   if (SQ_FAILED(sq_get(vm, -2))) return;
    00359
    00360   int idx = 0;
    00361   for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
    00362     sq_pushstring(vm, OTTD2SQ(*p), -1);
    00363     sq_pushinteger(vm, idx);
    00364     sq_rawset(vm, -3);
    00365   }
    00366
    00367   sq_pop(vm, 2);
    00368
    00369   ReconsiderGameScriptLanguage();
    00370 }
    00371
    00375 void ReconsiderGameScriptLanguage()
    00376 {
    00377   if (_current_data == NULL) return;
    00378
    00379   char temp[MAX_PATH];
    00380   strecpy(temp, _current_language->file, lastof(temp));
    00381
    00382   /* Remove the extension */
    00383   char *l = strrchr(temp, '.');
    00384   assert(l != NULL);
    00385   *l = '\0';
    00386
    00387   /* Skip the path */
    00388   char *language = strrchr(temp, PATHSEPCHAR);
    00389   assert(language != NULL);
    00390   language++;
    00391
    00392   for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
    00393     if (strcmp((*p)->language, language) == 0) {
    00394       _current_data->cur_language = *p;
    00395       return;
    00396     }
    00397   }
    00398
    00399   _current_data->cur_language = _current_data->compiled_strings[0];
    00400 }