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 "../language.h"
00014 #include "../strgen/strgen.h"
00015 #include "../debug.h"
00016 #include "../fileio_func.h"
00017 #include "../script/squirrel_class.hpp"
00018 #include "../strings_func.h"
00019 #include "game_text.hpp"
00020 #include "game.hpp"
00021 
00022 #include "table/strings.h"
00023 
00024 #include <exception>
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 
00064 LanguageStrings::LanguageStrings(const char *language)
00065 {
00066   const char *p = strrchr(language, PATHSEPCHAR);
00067   if (p == NULL) {
00068     p = language;
00069   } else {
00070     p++;
00071   }
00072 
00073   const char *e = strchr(p, '.');
00074   this->language = e == NULL ? strdup(p) : strndup(p, e - p);
00075 }
00076 
00078 LanguageStrings::~LanguageStrings()
00079 {
00080   free(this->language);
00081 }
00082 
00088 LanguageStrings *ReadRawLanguageStrings(const char *file)
00089 {
00090   LanguageStrings *ret = NULL;
00091   try {
00092     size_t to_read;
00093     FILE *fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
00094     if (fh == NULL) {
00095       return NULL;
00096     }
00097 
00098     ret = new LanguageStrings(file);
00099 
00100     char buffer[2048];
00101     while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != NULL) {
00102       size_t len = strlen(buffer);
00103 
00104       /* Remove trailing spaces/newlines from the string. */
00105       size_t i = len;
00106       while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
00107       buffer[i] = '\0';
00108 
00109       *ret->lines.Append() = strndup(buffer, to_read);
00110 
00111       if (len > to_read) {
00112         to_read = 0;
00113       } else {
00114         to_read -= len;
00115       }
00116     }
00117 
00118     return ret;
00119   } catch (...) {
00120     delete ret;
00121     return NULL;
00122   }
00123 }
00124 
00125 
00127 struct StringListReader : StringReader {
00128   const char * const *p;   
00129   const char * const *end; 
00130 
00138   StringListReader(StringData &data, const LanguageStrings *strings, bool master, bool translation) :
00139       StringReader(data, strings->language, master, translation), p(strings->lines.Begin()), end(strings->lines.End())
00140   {
00141   }
00142 
00143   /* virtual */ char *ReadLine(char *buffer, size_t size)
00144   {
00145     if (this->p == this->end) return NULL;
00146 
00147     strncpy(buffer, *this->p, size);
00148     this->p++;
00149 
00150     return buffer;
00151   }
00152 
00153   /* virtual */ void HandlePragma(char *str)
00154   {
00155     strgen_fatal("unknown pragma '%s'", str);
00156   }
00157 };
00158 
00160 struct TranslationWriter : LanguageWriter {
00161   StringList *strings; 
00162 
00167   TranslationWriter(StringList *strings) : strings(strings)
00168   {
00169   }
00170 
00171   void WriteHeader(const LanguagePackHeader *header)
00172   {
00173     /* We don't use the header. */
00174   }
00175 
00176   void Finalise()
00177   {
00178     /* Nothing to do. */
00179   }
00180 
00181   void WriteLength(uint length)
00182   {
00183     /* We don't write the length. */
00184   }
00185 
00186   void Write(const byte *buffer, size_t length)
00187   {
00188     *this->strings->Append() = strndup((const char*)buffer, length);
00189   }
00190 };
00191 
00193 struct StringNameWriter : HeaderWriter {
00194   StringList *strings; 
00195 
00200   StringNameWriter(StringList *strings) : strings(strings)
00201   {
00202   }
00203 
00204   void WriteStringID(const char *name, int stringid)
00205   {
00206     if (stringid == (int)this->strings->Length()) *this->strings->Append() = strdup(name);
00207   }
00208 
00209   void Finalise(const StringData &data)
00210   {
00211     /* Nothing to do. */
00212   }
00213 };
00214 
00215 static void GetBasePath(char *buffer, size_t length)
00216 {
00217   strecpy(buffer, Game::GetMainScript(), buffer + length);
00218   char *s = strrchr(buffer, PATHSEPCHAR);
00219   if (s != NULL) {
00220     /* Keep the PATHSEPCHAR there, remove the rest */
00221     s++;
00222     *s = '\0';
00223   }
00224 
00225   /* Tars dislike opening files with '/' on Windows.. so convert it to '\\' */
00226 #if (PATHSEPCHAR != '/')
00227   for (char *n = buffer; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
00228 #endif
00229 }
00230 
00234 class LanguageScanner : protected FileScanner {
00235 private:
00236   GameStrings *gs;
00237   char *exclude;
00238 
00239 public:
00241   LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(strdup(exclude)) {}
00242   ~LanguageScanner() { free(exclude); }
00243 
00247   void Scan(const char *directory)
00248   {
00249     this->FileScanner::Scan(".txt", directory, false);
00250   }
00251 
00252   /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
00253   {
00254     if (strcmp(filename, exclude) == 0) return true;
00255 
00256     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
00257     return true;
00258   }
00259 };
00260 
00265 GameStrings *LoadTranslations()
00266 {
00267   GameStrings *gs = new GameStrings();
00268   try {
00269     char filename[512];
00270     GetBasePath(filename, sizeof(filename));
00271     char *e = filename + strlen(filename);
00272 
00273     seprintf(e, filename + sizeof(filename), "lang" PATHSEP "english.txt");
00274     if (!FioCheckFileExists(filename, GAME_DIR)) throw std::exception();
00275     *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
00276 
00277     /* Scan for other language files */
00278     LanguageScanner scanner(gs, filename);
00279     strecpy(e, "lang" PATHSEP, filename + sizeof(filename));
00280     scanner.Scan(filename);
00281 
00282     gs->Compile();
00283     return gs;
00284   } catch (...) {
00285     delete gs;
00286     return NULL;
00287   }
00288 }
00289 
00291 void GameStrings::Compile()
00292 {
00293   StringData data(1);
00294   StringListReader master_reader(data, this->raw_strings[0], true, false);
00295   master_reader.ParseFile();
00296   if (_errors != 0) throw std::exception();
00297 
00298   this->version = data.Version();
00299 
00300   StringNameWriter id_writer(&this->string_names);
00301   id_writer.WriteHeader(data);
00302 
00303   for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
00304     data.FreeTranslation();
00305     StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
00306     translation_reader.ParseFile();
00307     if (_errors != 0) throw std::exception();
00308 
00309     LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
00310     TranslationWriter writer(&compiled->lines);
00311     writer.WriteLang(data);
00312   }
00313 }
00314 
00316 GameStrings *_current_data = NULL;
00317 
00323 const char *GetGameStringPtr(uint id)
00324 {
00325   if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
00326   return _current_data->cur_language->lines[id];
00327 }
00328 
00333 void RegisterGameTranslation(Squirrel *engine)
00334 {
00335   delete _current_data;
00336   _current_data = LoadTranslations();
00337   if (_current_data == NULL) return;
00338 
00339   HSQUIRRELVM vm = engine->GetVM();
00340   sq_pushroottable(vm);
00341   sq_pushstring(vm, _SC("GSText"), -1);
00342   if (SQ_FAILED(sq_get(vm, -2))) return;
00343 
00344   int idx = 0;
00345   for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
00346     sq_pushstring(vm, OTTD2SQ(*p), -1);
00347     sq_pushinteger(vm, idx);
00348     sq_rawset(vm, -3);
00349   }
00350 
00351   sq_pop(vm, 2);
00352 
00353   ReconsiderGameScriptLanguage();
00354 }
00355 
00359 void ReconsiderGameScriptLanguage()
00360 {
00361   if (_current_data == NULL) return;
00362 
00363   char temp[MAX_PATH];
00364   strecpy(temp, _current_language->file, temp + sizeof(temp));
00365 
00366   /* Remove the extension */
00367   char *l = strrchr(temp, '.');
00368   assert(l != NULL);
00369   *l = '\0';
00370 
00371   /* Skip the path */
00372   char *language = strrchr(temp, PATHSEPCHAR);
00373   assert(language != NULL);
00374   language++;
00375 
00376   for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
00377     if (strcmp((*p)->language, language) == 0) {
00378       _current_data->cur_language = *p;
00379       return;
00380     }
00381   }
00382 
00383   _current_data->cur_language = _current_data->compiled_strings[0];
00384 }