OpenTTD
game_text.cpp
Go to the documentation of this file.
1 /* $Id: game_text.cpp 26774 2014-09-06 17:46:56Z rubidium $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * 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.
6  * 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.
7  * 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/>.
8  */
9 
12 #include "../stdafx.h"
13 #include "../strgen/strgen.h"
14 #include "../debug.h"
15 #include "../fileio_func.h"
16 #include "../tar_type.h"
17 #include "../script/squirrel_class.hpp"
18 #include "../strings_func.h"
19 #include "game_text.hpp"
20 #include "game.hpp"
21 #include "game_info.hpp"
22 
23 #include "table/strings.h"
24 
25 #include <stdarg.h>
26 
27 #include "../safeguards.h"
28 
29 void CDECL strgen_warning(const char *s, ...)
30 {
31  char buf[1024];
32  va_list va;
33  va_start(va, s);
34  vseprintf(buf, lastof(buf), s, va);
35  va_end(va);
36  DEBUG(script, 0, "%s:%d: warning: %s", _file, _cur_line, buf);
37  _warnings++;
38 }
39 
40 void CDECL strgen_error(const char *s, ...)
41 {
42  char buf[1024];
43  va_list va;
44  va_start(va, s);
45  vseprintf(buf, lastof(buf), s, va);
46  va_end(va);
47  DEBUG(script, 0, "%s:%d: error: %s", _file, _cur_line, buf);
48  _errors++;
49 }
50 
51 void NORETURN CDECL strgen_fatal(const char *s, ...)
52 {
53  char buf[1024];
54  va_list va;
55  va_start(va, s);
56  vseprintf(buf, lastof(buf), s, va);
57  va_end(va);
58  DEBUG(script, 0, "%s:%d: FATAL: %s", _file, _cur_line, buf);
59  throw std::exception();
60 }
61 
67 LanguageStrings::LanguageStrings(const char *language, const char *end)
68 {
69  this->language = stredup(language, end != NULL ? end - 1 : NULL);
70 }
71 
74 {
75  free(this->language);
76 }
77 
84 {
85  LanguageStrings *ret = NULL;
86  FILE *fh = NULL;
87  try {
88  size_t to_read;
89  fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
90  if (fh == NULL) {
91  return NULL;
92  }
93 
94  const char *langname = strrchr(file, PATHSEPCHAR);
95  if (langname == NULL) {
96  langname = file;
97  } else {
98  langname++;
99  }
100 
101  /* Check for invalid empty filename */
102  if (*langname == '.' || *langname == 0) {
103  fclose(fh);
104  return NULL;
105  }
106 
107  ret = new LanguageStrings(langname, strchr(langname, '.'));
108 
109  char buffer[2048];
110  while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != NULL) {
111  size_t len = strlen(buffer);
112 
113  /* Remove trailing spaces/newlines from the string. */
114  size_t i = len;
115  while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
116  buffer[i] = '\0';
117 
118  *ret->lines.Append() = stredup(buffer, buffer + to_read - 1);
119 
120  if (len > to_read) {
121  to_read = 0;
122  } else {
123  to_read -= len;
124  }
125  }
126 
127  fclose(fh);
128  return ret;
129  } catch (...) {
130  if (fh != NULL) fclose(fh);
131  delete ret;
132  return NULL;
133  }
134 }
135 
136 
139  const char * const *p;
140  const char * const *end;
141 
150  StringReader(data, strings->language, master, translation), p(strings->lines.Begin()), end(strings->lines.End())
151  {
152  }
153 
154  /* virtual */ char *ReadLine(char *buffer, const char *last)
155  {
156  if (this->p == this->end) return NULL;
157 
158  strecpy(buffer, *this->p, last);
159  this->p++;
160 
161  return buffer;
162  }
163 };
164 
168 
173  TranslationWriter(StringList *strings) : strings(strings)
174  {
175  }
176 
177  void WriteHeader(const LanguagePackHeader *header)
178  {
179  /* We don't use the header. */
180  }
181 
182  void Finalise()
183  {
184  /* Nothing to do. */
185  }
186 
187  void WriteLength(uint length)
188  {
189  /* We don't write the length. */
190  }
191 
192  void Write(const byte *buffer, size_t length)
193  {
194  char *dest = MallocT<char>(length + 1);
195  memcpy(dest, buffer, length);
196  dest[length] = '\0';
197  *this->strings->Append() = dest;
198  }
199 };
200 
204 
209  StringNameWriter(StringList *strings) : strings(strings)
210  {
211  }
212 
213  void WriteStringID(const char *name, int stringid)
214  {
215  if (stringid == (int)this->strings->Length()) *this->strings->Append() = stredup(name);
216  }
217 
218  void Finalise(const StringData &data)
219  {
220  /* Nothing to do. */
221  }
222 };
223 
227 class LanguageScanner : protected FileScanner {
228 private:
229  GameStrings *gs;
230  char *exclude;
231 
232 public:
234  LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(stredup(exclude)) {}
235  ~LanguageScanner() { free(exclude); }
236 
240  void Scan(const char *directory)
241  {
242  this->FileScanner::Scan(".txt", directory, false);
243  }
244 
245  /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
246  {
247  if (strcmp(filename, exclude) == 0) return true;
248 
249  *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
250  return true;
251  }
252 };
253 
259 {
260  const GameInfo *info = Game::GetInfo();
261  char filename[512];
262  strecpy(filename, info->GetMainScript(), lastof(filename));
263  char *e = strrchr(filename, PATHSEPCHAR);
264  if (e == NULL) return NULL;
265  e++; // Make 'e' point after the PATHSEPCHAR
266 
267  strecpy(e, "lang" PATHSEP "english.txt", lastof(filename));
268  if (!FioCheckFileExists(filename, GAME_DIR)) return NULL;
269 
270  GameStrings *gs = new GameStrings();
271  try {
272  *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
273 
274  /* Scan for other language files */
275  LanguageScanner scanner(gs, filename);
276  strecpy(e, "lang" PATHSEP, lastof(filename));
277  size_t len = strlen(filename);
278 
279  const char *tar_filename = info->GetTarFile();
280  TarList::iterator iter;
281  if (tar_filename != NULL && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
282  /* The main script is in a tar file, so find all files that
283  * are in the same tar and add them to the langfile scanner. */
284  TarFileList::iterator tar;
285  FOR_ALL_TARS(tar, GAME_DIR) {
286  /* Not in the same tar. */
287  if (tar->second.tar_filename != iter->first) continue;
288 
289  /* Check the path and extension. */
290  if (tar->first.size() <= len || tar->first.compare(0, len, filename) != 0) continue;
291  if (tar->first.compare(tar->first.size() - 4, 4, ".txt") != 0) continue;
292 
293  scanner.AddFile(tar->first.c_str(), 0, tar_filename);
294  }
295  } else {
296  /* Scan filesystem */
297  scanner.Scan(filename);
298  }
299 
300  gs->Compile();
301  return gs;
302  } catch (...) {
303  delete gs;
304  return NULL;
305  }
306 }
307 
310 {
311  StringData data(1);
312  StringListReader master_reader(data, this->raw_strings[0], true, false);
313  master_reader.ParseFile();
314  if (_errors != 0) throw std::exception();
315 
316  this->version = data.Version();
317 
318  StringNameWriter id_writer(&this->string_names);
319  id_writer.WriteHeader(data);
320 
321  for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
322  data.FreeTranslation();
323  StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
324  translation_reader.ParseFile();
325  if (_errors != 0) throw std::exception();
326 
327  LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
328  TranslationWriter writer(&compiled->lines);
329  writer.WriteLang(data);
330  }
331 }
332 
335 
341 const char *GetGameStringPtr(uint id)
342 {
343  if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
344  return _current_data->cur_language->lines[id];
345 }
346 
352 {
353  delete _current_data;
354  _current_data = LoadTranslations();
355  if (_current_data == NULL) return;
356 
357  HSQUIRRELVM vm = engine->GetVM();
358  sq_pushroottable(vm);
359  sq_pushstring(vm, "GSText", -1);
360  if (SQ_FAILED(sq_get(vm, -2))) return;
361 
362  int idx = 0;
363  for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
364  sq_pushstring(vm, *p, -1);
365  sq_pushinteger(vm, idx);
366  sq_rawset(vm, -3);
367  }
368 
369  sq_pop(vm, 2);
370 
372 }
373 
378 {
379  if (_current_data == NULL) return;
380 
381  char temp[MAX_PATH];
382  strecpy(temp, _current_language->file, lastof(temp));
383 
384  /* Remove the extension */
385  char *l = strrchr(temp, '.');
386  assert(l != NULL);
387  *l = '\0';
388 
389  /* Skip the path */
390  char *language = strrchr(temp, PATHSEPCHAR);
391  assert(language != NULL);
392  language++;
393 
394  for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
395  if (strcmp((*p)->language, language) == 0) {
396  _current_data->cur_language = *p;
397  return;
398  }
399  }
400 
401  _current_data->cur_language = _current_data->compiled_strings[0];
402 }