# game_text.cpp

Go to the documentation of this file.

    00001 /* $Id$ */
    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 "../script/squirrel_class.hpp"
    00017 #include "../strings_func.h"
    00018 #include "game_text.hpp"
    00019 #include "game.hpp"
    00020
    00021 #include "table/strings.h"
    00022
    00023 #include <stdarg.h>
    00024
    00025 void CDECL strgen_warning(const char *s, ...)
    00026 {
    00027   char buf[1024];
    00028   va_list va;
    00029   va_start(va, s);
    00030   vsnprintf(buf, lengthof(buf), s, va);
    00031   va_end(va);
    00032   DEBUG(script, 0, "%s:%d: warning: %s", _file, _cur_line, buf);
    00033   _warnings++;
    00034 }
    00035
    00036 void CDECL strgen_error(const char *s, ...)
    00037 {
    00038   char buf[1024];
    00039   va_list va;
    00040   va_start(va, s);
    00041   vsnprintf(buf, lengthof(buf), s, va);
    00042   va_end(va);
    00043   DEBUG(script, 0, "%s:%d: error: %s", _file, _cur_line, buf);
    00044   _errors++;
    00045 }
    00046
    00047 void NORETURN CDECL strgen_fatal(const char *s, ...)
    00048 {
    00049   char buf[1024];
    00050   va_list va;
    00051   va_start(va, s);
    00052   vsnprintf(buf, lengthof(buf), s, va);
    00053   va_end(va);
    00054   DEBUG(script, 0, "%s:%d: FATAL: %s", _file, _cur_line, buf);
    00055   throw std::exception();
    00056 }
    00057
    00062 LanguageStrings::LanguageStrings(const char *language)
    00063 {
    00064   const char *p = strrchr(language, PATHSEPCHAR);
    00065   if (p == NULL) {
    00066     p = language;
    00067   } else {
    00068     p++;
    00069   }
    00070
    00071   const char *e = strchr(p, '.');
    00072   this->language = e == NULL ? strdup(p) : strndup(p, e - p);
    00073 }
    00074
    00076 LanguageStrings::~LanguageStrings()
    00077 {
    00078   free(this->language);
    00079 }
    00080
    00086 LanguageStrings *ReadRawLanguageStrings(const char *file)
    00087 {
    00088   LanguageStrings *ret = NULL;
    00089   try {
    00090     size_t to_read;
    00091     FILE *fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
    00092     if (fh == NULL) {
    00093       return NULL;
    00094     }
    00095
    00096     ret = new LanguageStrings(file);
    00097
    00098     char buffer[2048];
    00099     while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != NULL) {
    00100       size_t len = strlen(buffer);
    00101
    00102       /* Remove trailing spaces/newlines from the string. */
    00103       size_t i = len;
    00104       while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
    00105       buffer[i] = '\0';
    00106
    00107       *ret->lines.Append() = strndup(buffer, to_read);
    00108
    00109       if (len > to_read) {
    00110         to_read = 0;
    00111       } else {
    00112         to_read -= len;
    00113       }
    00114     }
    00115
    00116     return ret;
    00117   } catch (...) {
    00118     delete ret;
    00119     return NULL;
    00120   }
    00121 }
    00122
    00123
    00125 struct StringListReader : StringReader {
    00126   const char * const *p;
    00127   const char * const *end;
    00128
    00136   StringListReader(StringData &data, const LanguageStrings *strings, bool master, bool translation) :
    00137       StringReader(data, strings->language, master, translation), p(strings->lines.Begin()), end(strings->lines.End())
    00138   {
    00139   }
    00140
    00141   /* virtual */ char *ReadLine(char *buffer, size_t size)
    00142   {
    00143     if (this->p == this->end) return NULL;
    00144
    00145     strncpy(buffer, *this->p, size);
    00146     this->p++;
    00147
    00148     return buffer;
    00149   }
    00150 };
    00151
    00153 struct TranslationWriter : LanguageWriter {
    00154   StringList *strings;
    00155
    00160   TranslationWriter(StringList *strings) : strings(strings)
    00161   {
    00162   }
    00163
    00164   void WriteHeader(const LanguagePackHeader *header)
    00165   {
    00166     /* We don't use the header. */
    00167   }
    00168
    00169   void Finalise()
    00170   {
    00171     /* Nothing to do. */
    00172   }
    00173
    00174   void WriteLength(uint length)
    00175   {
    00176     /* We don't write the length. */
    00177   }
    00178
    00179   void Write(const byte *buffer, size_t length)
    00180   {
    00181     char *dest = MallocT<char>(length + 1);
    00182     memcpy(dest, buffer, length);
    00183     dest[length] = '\0';
    00184     *this->strings->Append() = dest;
    00185   }
    00186 };
    00187
    00189 struct StringNameWriter : HeaderWriter {
    00190   StringList *strings;
    00191
    00196   StringNameWriter(StringList *strings) : strings(strings)
    00197   {
    00198   }
    00199
    00200   void WriteStringID(const char *name, int stringid)
    00201   {
    00202     if (stringid == (int)this->strings->Length()) *this->strings->Append() = strdup(name);
    00203   }
    00204
    00205   void Finalise(const StringData &data)
    00206   {
    00207     /* Nothing to do. */
    00208   }
    00209 };
    00210
    00211 static void GetBasePath(char *buffer, size_t length)
    00212 {
    00213   strecpy(buffer, Game::GetMainScript(), buffer + length);
    00214   char *s = strrchr(buffer, PATHSEPCHAR);
    00215   if (s != NULL) {
    00216     /* Keep the PATHSEPCHAR there, remove the rest */
    00217     s++;
    00218     *s = '\0';
    00219   }
    00220
    00221   /* Tars dislike opening files with '/' on Windows.. so convert it to '\\' */
    00222 #if (PATHSEPCHAR != '/')
    00223   for (char *n = buffer; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
    00224 #endif
    00225 }
    00226
    00230 class LanguageScanner : protected FileScanner {
    00231 private:
    00232   GameStrings *gs;
    00233   char *exclude;
    00234
    00235 public:
    00237   LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(strdup(exclude)) {}
    00238   ~LanguageScanner() { free(exclude); }
    00239
    00243   void Scan(const char *directory)
    00244   {
    00245     this->FileScanner::Scan(".txt", directory, false);
    00246   }
    00247
    00248   /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
    00249   {
    00250     if (strcmp(filename, exclude) == 0) return true;
    00251
    00252     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
    00253     return true;
    00254   }
    00255 };
    00256
    00261 GameStrings *LoadTranslations()
    00262 {
    00263   GameStrings *gs = new GameStrings();
    00264   try {
    00265     char filename[512];
    00266     GetBasePath(filename, sizeof(filename));
    00267     char *e = filename + strlen(filename);
    00268
    00269     seprintf(e, filename + sizeof(filename), "lang" PATHSEP "english.txt");
    00270     if (!FioCheckFileExists(filename, GAME_DIR)) throw std::exception();
    00271     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
    00272
    00273     /* Scan for other language files */
    00274     LanguageScanner scanner(gs, filename);
    00275     strecpy(e, "lang" PATHSEP, filename + sizeof(filename));
    00276     scanner.Scan(filename);
    00277
    00278     gs->Compile();
    00279     return gs;
    00280   } catch (...) {
    00281     delete gs;
    00282     return NULL;
    00283   }
    00284 }
    00285
    00287 void GameStrings::Compile()
    00288 {
    00289   StringData data(1);
    00290   StringListReader master_reader(data, this->raw_strings[0], true, false);
    00291   master_reader.ParseFile();
    00292   if (_errors != 0) throw std::exception();
    00293
    00294   this->version = data.Version();
    00295
    00296   StringNameWriter id_writer(&this->string_names);
    00297   id_writer.WriteHeader(data);
    00298
    00299   for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
    00300     data.FreeTranslation();
    00301     StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
    00302     translation_reader.ParseFile();
    00303     if (_errors != 0) throw std::exception();
    00304
    00305     LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
    00306     TranslationWriter writer(&compiled->lines);
    00307     writer.WriteLang(data);
    00308   }
    00309 }
    00310
    00312 GameStrings *_current_data = NULL;
    00313
    00319 const char *GetGameStringPtr(uint id)
    00320 {
    00321   if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
    00322   return _current_data->cur_language->lines[id];
    00323 }
    00324
    00329 void RegisterGameTranslation(Squirrel *engine)
    00330 {
    00331   delete _current_data;
    00332   _current_data = LoadTranslations();
    00333   if (_current_data == NULL) return;
    00334
    00335   HSQUIRRELVM vm = engine->GetVM();
    00336   sq_pushroottable(vm);
    00337   sq_pushstring(vm, _SC("GSText"), -1);
    00338   if (SQ_FAILED(sq_get(vm, -2))) return;
    00339
    00340   int idx = 0;
    00341   for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
    00342     sq_pushstring(vm, OTTD2SQ(*p), -1);
    00343     sq_pushinteger(vm, idx);
    00344     sq_rawset(vm, -3);
    00345   }
    00346
    00347   sq_pop(vm, 2);
    00348
    00349   ReconsiderGameScriptLanguage();
    00350 }
    00351
    00355 void ReconsiderGameScriptLanguage()
    00356 {
    00357   if (_current_data == NULL) return;
    00358
    00359   char temp[MAX_PATH];
    00360   strecpy(temp, _current_language->file, temp + sizeof(temp));
    00361
    00362   /* Remove the extension */
    00363   char *l = strrchr(temp, '.');
    00364   assert(l != NULL);
    00365   *l = '\0';
    00366
    00367   /* Skip the path */
    00368   char *language = strrchr(temp, PATHSEPCHAR);
    00369   assert(language != NULL);
    00370   language++;
    00371
    00372   for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
    00373     if (strcmp((*p)->language, language) == 0) {
    00374       _current_data->cur_language = *p;
    00375       return;
    00376     }
    00377   }
    00378
    00379   _current_data->cur_language = _current_data->compiled_strings[0];
    00380 }