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   /* virtual */ void HandlePragma(char *str)
00152   {
00153     strgen_fatal("unknown pragma '%s'", str);
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     *this->strings->Append() = strndup((const char*)buffer, length);
00187   }
00188 };
00189 
00191 struct StringNameWriter : HeaderWriter {
00192   StringList *strings; 
00193 
00198   StringNameWriter(StringList *strings) : strings(strings)
00199   {
00200   }
00201 
00202   void WriteStringID(const char *name, int stringid)
00203   {
00204     if (stringid == (int)this->strings->Length()) *this->strings->Append() = strdup(name);
00205   }
00206 
00207   void Finalise(const StringData &data)
00208   {
00209     /* Nothing to do. */
00210   }
00211 };
00212 
00213 static void GetBasePath(char *buffer, size_t length)
00214 {
00215   strecpy(buffer, Game::GetMainScript(), buffer + length);
00216   char *s = strrchr(buffer, PATHSEPCHAR);
00217   if (s != NULL) {
00218     /* Keep the PATHSEPCHAR there, remove the rest */
00219     s++;
00220     *s = '\0';
00221   }
00222 
00223   /* Tars dislike opening files with '/' on Windows.. so convert it to '\\' */
00224 #if (PATHSEPCHAR != '/')
00225   for (char *n = buffer; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
00226 #endif
00227 }
00228 
00232 class LanguageScanner : protected FileScanner {
00233 private:
00234   GameStrings *gs;
00235   char *exclude;
00236 
00237 public:
00239   LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(strdup(exclude)) {}
00240   ~LanguageScanner() { free(exclude); }
00241 
00245   void Scan(const char *directory)
00246   {
00247     this->FileScanner::Scan(".txt", directory, false);
00248   }
00249 
00250   /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
00251   {
00252     if (strcmp(filename, exclude) == 0) return true;
00253 
00254     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
00255     return true;
00256   }
00257 };
00258 
00263 GameStrings *LoadTranslations()
00264 {
00265   GameStrings *gs = new GameStrings();
00266   try {
00267     char filename[512];
00268     GetBasePath(filename, sizeof(filename));
00269     char *e = filename + strlen(filename);
00270 
00271     seprintf(e, filename + sizeof(filename), "lang" PATHSEP "english.txt");
00272     if (!FioCheckFileExists(filename, GAME_DIR)) throw std::exception();
00273     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
00274 
00275     /* Scan for other language files */
00276     LanguageScanner scanner(gs, filename);
00277     strecpy(e, "lang" PATHSEP, filename + sizeof(filename));
00278     scanner.Scan(filename);
00279 
00280     gs->Compile();
00281     return gs;
00282   } catch (...) {
00283     delete gs;
00284     return NULL;
00285   }
00286 }
00287 
00289 void GameStrings::Compile()
00290 {
00291   StringData data(1);
00292   StringListReader master_reader(data, this->raw_strings[0], true, false);
00293   master_reader.ParseFile();
00294   if (_errors != 0) throw std::exception();
00295 
00296   this->version = data.Version();
00297 
00298   StringNameWriter id_writer(&this->string_names);
00299   id_writer.WriteHeader(data);
00300 
00301   for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
00302     data.FreeTranslation();
00303     StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
00304     translation_reader.ParseFile();
00305     if (_errors != 0) throw std::exception();
00306 
00307     LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
00308     TranslationWriter writer(&compiled->lines);
00309     writer.WriteLang(data);
00310   }
00311 }
00312 
00314 GameStrings *_current_data = NULL;
00315 
00321 const char *GetGameStringPtr(uint id)
00322 {
00323   if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
00324   return _current_data->cur_language->lines[id];
00325 }
00326 
00331 void RegisterGameTranslation(Squirrel *engine)
00332 {
00333   delete _current_data;
00334   _current_data = LoadTranslations();
00335   if (_current_data == NULL) return;
00336 
00337   HSQUIRRELVM vm = engine->GetVM();
00338   sq_pushroottable(vm);
00339   sq_pushstring(vm, _SC("GSText"), -1);
00340   if (SQ_FAILED(sq_get(vm, -2))) return;
00341 
00342   int idx = 0;
00343   for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
00344     sq_pushstring(vm, OTTD2SQ(*p), -1);
00345     sq_pushinteger(vm, idx);
00346     sq_rawset(vm, -3);
00347   }
00348 
00349   sq_pop(vm, 2);
00350 
00351   ReconsiderGameScriptLanguage();
00352 }
00353 
00357 void ReconsiderGameScriptLanguage()
00358 {
00359   if (_current_data == NULL) return;
00360 
00361   char temp[MAX_PATH];
00362   strecpy(temp, _current_language->file, temp + sizeof(temp));
00363 
00364   /* Remove the extension */
00365   char *l = strrchr(temp, '.');
00366   assert(l != NULL);
00367   *l = '\0';
00368 
00369   /* Skip the path */
00370   char *language = strrchr(temp, PATHSEPCHAR);
00371   assert(language != NULL);
00372   language++;
00373 
00374   for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
00375     if (strcmp((*p)->language, language) == 0) {
00376       _current_data->cur_language = *p;
00377       return;
00378     }
00379   }
00380 
00381   _current_data->cur_language = _current_data->compiled_strings[0];
00382 }