OpenTTD
game_text.cpp
Go to the documentation of this file.
1 /* $Id$ */
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 #include <memory>
27 
28 #include "../safeguards.h"
29 
30 void CDECL strgen_warning(const char *s, ...)
31 {
32  char buf[1024];
33  va_list va;
34  va_start(va, s);
35  vseprintf(buf, lastof(buf), s, va);
36  va_end(va);
37  DEBUG(script, 0, "%s:%d: warning: %s", _file, _cur_line, buf);
38  _warnings++;
39 }
40 
41 void CDECL strgen_error(const char *s, ...)
42 {
43  char buf[1024];
44  va_list va;
45  va_start(va, s);
46  vseprintf(buf, lastof(buf), s, va);
47  va_end(va);
48  DEBUG(script, 0, "%s:%d: error: %s", _file, _cur_line, buf);
49  _errors++;
50 }
51 
52 void NORETURN CDECL strgen_fatal(const char *s, ...)
53 {
54  char buf[1024];
55  va_list va;
56  va_start(va, s);
57  vseprintf(buf, lastof(buf), s, va);
58  va_end(va);
59  DEBUG(script, 0, "%s:%d: FATAL: %s", _file, _cur_line, buf);
60  throw std::exception();
61 }
62 
68 LanguageStrings::LanguageStrings(const char *language, const char *end)
69 {
70  this->language = stredup(language, end != nullptr ? end - 1 : nullptr);
71 }
72 
75 {
76  free(this->language);
77 }
78 
84 std::unique_ptr<LanguageStrings> ReadRawLanguageStrings(const char *file)
85 {
86  try {
87  size_t to_read;
88  FILE *fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
89  if (fh == nullptr) return nullptr;
90 
91  FileCloser fhClose(fh);
92 
93  const char *langname = strrchr(file, PATHSEPCHAR);
94  if (langname == nullptr) {
95  langname = file;
96  } else {
97  langname++;
98  }
99 
100  /* Check for invalid empty filename */
101  if (*langname == '.' || *langname == 0) return nullptr;
102 
103  std::unique_ptr<LanguageStrings> ret(new LanguageStrings(langname, strchr(langname, '.')));
104 
105  char buffer[2048];
106  while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != nullptr) {
107  size_t len = strlen(buffer);
108 
109  /* Remove trailing spaces/newlines from the string. */
110  size_t i = len;
111  while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
112  buffer[i] = '\0';
113 
114  ret->lines.emplace_back(buffer, i);
115 
116  if (len > to_read) {
117  to_read = 0;
118  } else {
119  to_read -= len;
120  }
121  }
122 
123  return ret;
124  } catch (...) {
125  return nullptr;
126  }
127 }
128 
129 
132  StringList::const_iterator p;
133  StringList::const_iterator end;
134 
142  StringListReader(StringData &data, const LanguageStrings &strings, bool master, bool translation) :
143  StringReader(data, strings.language, master, translation), p(strings.lines.begin()), end(strings.lines.end())
144  {
145  }
146 
147  char *ReadLine(char *buffer, const char *last) override
148  {
149  if (this->p == this->end) return nullptr;
150 
151  strecpy(buffer, this->p->c_str(), last);
152  this->p++;
153 
154  return buffer;
155  }
156 };
157 
161 
166  TranslationWriter(StringList &strings) : strings(strings)
167  {
168  }
169 
170  void WriteHeader(const LanguagePackHeader *header)
171  {
172  /* We don't use the header. */
173  }
174 
175  void Finalise()
176  {
177  /* Nothing to do. */
178  }
179 
180  void WriteLength(uint length)
181  {
182  /* We don't write the length. */
183  }
184 
185  void Write(const byte *buffer, size_t length)
186  {
187  this->strings.emplace_back((const char *)buffer, length);
188  }
189 };
190 
194 
199  StringNameWriter(StringList &strings) : strings(strings)
200  {
201  }
202 
203  void WriteStringID(const char *name, int stringid)
204  {
205  if (stringid == (int)this->strings.size()) this->strings.emplace_back(name);
206  }
207 
208  void Finalise(const StringData &data)
209  {
210  /* Nothing to do. */
211  }
212 };
213 
217 class LanguageScanner : protected FileScanner {
218 private:
219  GameStrings *gs;
220  char *exclude;
221 
222 public:
224  LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(stredup(exclude)) {}
225  ~LanguageScanner() { free(exclude); }
226 
230  void Scan(const char *directory)
231  {
232  this->FileScanner::Scan(".txt", directory, false);
233  }
234 
235  bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override
236  {
237  if (strcmp(filename, exclude) == 0) return true;
238 
239  auto ls = ReadRawLanguageStrings(filename);
240  if (ls == nullptr) return false;
241 
242  gs->raw_strings.push_back(std::move(ls));
243  return true;
244  }
245 };
246 
252 {
253  const GameInfo *info = Game::GetInfo();
254  char filename[512];
255  strecpy(filename, info->GetMainScript(), lastof(filename));
256  char *e = strrchr(filename, PATHSEPCHAR);
257  if (e == nullptr) return nullptr;
258  e++; // Make 'e' point after the PATHSEPCHAR
259 
260  strecpy(e, "lang" PATHSEP "english.txt", lastof(filename));
261  if (!FioCheckFileExists(filename, GAME_DIR)) return nullptr;
262 
263  auto ls = ReadRawLanguageStrings(filename);
264  if (ls == nullptr) return nullptr;
265 
266  GameStrings *gs = new GameStrings();
267  try {
268  gs->raw_strings.push_back(std::move(ls));
269 
270  /* Scan for other language files */
271  LanguageScanner scanner(gs, filename);
272  strecpy(e, "lang" PATHSEP, lastof(filename));
273  size_t len = strlen(filename);
274 
275  const char *tar_filename = info->GetTarFile();
276  TarList::iterator iter;
277  if (tar_filename != nullptr && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
278  /* The main script is in a tar file, so find all files that
279  * are in the same tar and add them to the langfile scanner. */
280  TarFileList::iterator tar;
281  FOR_ALL_TARS(tar, GAME_DIR) {
282  /* Not in the same tar. */
283  if (tar->second.tar_filename != iter->first) continue;
284 
285  /* Check the path and extension. */
286  if (tar->first.size() <= len || tar->first.compare(0, len, filename) != 0) continue;
287  if (tar->first.compare(tar->first.size() - 4, 4, ".txt") != 0) continue;
288 
289  scanner.AddFile(tar->first.c_str(), 0, tar_filename);
290  }
291  } else {
292  /* Scan filesystem */
293  scanner.Scan(filename);
294  }
295 
296  gs->Compile();
297  return gs;
298  } catch (...) {
299  delete gs;
300  return nullptr;
301  }
302 }
303 
306 {
307  StringData data(32);
308  StringListReader master_reader(data, *this->raw_strings[0], true, false);
309  master_reader.ParseFile();
310  if (_errors != 0) throw std::exception();
311 
312  this->version = data.Version();
313 
314  StringNameWriter id_writer(this->string_names);
315  id_writer.WriteHeader(data);
316 
317  for (const auto &p : this->raw_strings) {
318  data.FreeTranslation();
319  StringListReader translation_reader(data, *p, false, strcmp(p->language, "english") != 0);
320  translation_reader.ParseFile();
321  if (_errors != 0) throw std::exception();
322 
323  this->compiled_strings.emplace_back(new LanguageStrings(p->language));
324  TranslationWriter writer(this->compiled_strings.back()->lines);
325  writer.WriteLang(data);
326  }
327 }
328 
331 
337 const char *GetGameStringPtr(uint id)
338 {
339  if (id >= _current_data->cur_language->lines.size()) return GetStringPtr(STR_UNDEFINED);
340  return _current_data->cur_language->lines[id].c_str();
341 }
342 
348 {
349  delete _current_data;
350  _current_data = LoadTranslations();
351  if (_current_data == nullptr) return;
352 
353  HSQUIRRELVM vm = engine->GetVM();
354  sq_pushroottable(vm);
355  sq_pushstring(vm, "GSText", -1);
356  if (SQ_FAILED(sq_get(vm, -2))) return;
357 
358  int idx = 0;
359  for (const auto &p : _current_data->string_names) {
360  sq_pushstring(vm, p.c_str(), -1);
361  sq_pushinteger(vm, idx);
362  sq_rawset(vm, -3);
363  idx++;
364  }
365 
366  sq_pop(vm, 2);
367 
369 }
370 
375 {
376  if (_current_data == nullptr) return;
377 
378  char temp[MAX_PATH];
379  strecpy(temp, _current_language->file, lastof(temp));
380 
381  /* Remove the extension */
382  char *l = strrchr(temp, '.');
383  assert(l != nullptr);
384  *l = '\0';
385 
386  /* Skip the path */
387  char *language = strrchr(temp, PATHSEPCHAR);
388  assert(language != nullptr);
389  language++;
390 
391  for (auto &p : _current_data->compiled_strings) {
392  if (strcmp(p->language, language) == 0) {
393  _current_data->cur_language = p;
394  return;
395  }
396  }
397 
398  _current_data->cur_language = _current_data->compiled_strings[0];
399 }
std::vector< std::shared_ptr< LanguageStrings > > compiled_strings
The compiled strings per language, first must be English/the master language!.
Definition: game_text.hpp:36
LanguageStrings(const char *language, const char *end=nullptr)
Create a new container for language strings.
Definition: game_text.cpp:68
Class for writing an encoded language.
Definition: game_text.cpp:159
virtual void WriteLang(const StringData &data)
Actually write the language.
int _cur_line
The current line we&#39;re parsing in the input file.
Definition: strgen_base.cpp:29
Container for all the game strings.
Definition: game_text.hpp:31
void WriteStringID(const char *name, int stringid)
Write the string ID.
Definition: game_text.cpp:203
A reader that simply reads using fopen.
Definition: game_text.cpp:131
void RegisterGameTranslation(Squirrel *engine)
Register the current translation to the Squirrel engine.
Definition: game_text.cpp:347
uint Scan(const char *extension, Subdirectory sd, bool tars=true, bool recursive=true)
Scan for files with the given extension in the given search path.
Definition: fileio.cpp:1375
int CDECL vseprintf(char *str, const char *last, const char *format, va_list ap)
Safer implementation of vsnprintf; same as vsnprintf except:
Definition: string.cpp:62
const LanguageMetadata * _current_language
The currently loaded language.
Definition: strings.cpp:48
Base functions regarding game texts.
Subdirectory for all game scripts.
Definition: fileio_type.h:123
#define lastof(x)
Get the last element of an fixed size array.
Definition: depend.cpp:50
char * ReadLine(char *buffer, const char *last) override
Read a single line from the source of strings.
Definition: game_text.cpp:147
All static information from an Game like name, version, etc.
Definition: game_info.hpp:18
Helper for scanning for files with a given name.
Definition: fileio_func.h:72
const char * GetMainScript() const
Get the filename of the main.nut script.
Definition: script_info.hpp:94
Information about the currently known strings.
Definition: strgen.h:43
TranslationWriter(StringList &strings)
Writer for the encoded data.
Definition: game_text.cpp:166
void Scan(const char *directory)
Scan.
Definition: game_text.cpp:230
LanguageScanner(GameStrings *gs, const char *exclude)
Initialise.
Definition: game_text.cpp:224
Header of a language file.
Definition: language.h:26
Scanner to find language files in a GameScript directory.
Definition: game_text.cpp:217
void ReconsiderGameScriptLanguage()
Reconsider the game script language, so we use the right one.
Definition: game_text.cpp:374
void Compile()
Compile the language.
Definition: game_text.cpp:305
FILE * FioFOpenFile(const char *filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition: fileio.cpp:465
GameStrings * _current_data
The currently loaded game strings.
Definition: game_text.cpp:330
Container for the raw (unencoded) language strings of a language.
Definition: game_text.hpp:22
StringList lines
The lines of the file to pass into the parser/encoder.
Definition: game_text.hpp:24
Auto-close a file upon scope exit.
Definition: fileio_func.h:153
uint Version() const
Make a hash of the file to get a unique "version number".
char * stredup(const char *s, const char *last)
Create a duplicate of the given string.
Definition: string.cpp:138
StringList::const_iterator p
The current location of the iteration.
Definition: game_text.cpp:132
void WriteHeader(const LanguagePackHeader *header)
Write the header metadata.
Definition: game_text.cpp:170
GameStrings * LoadTranslations()
Load all translations that we know of.
Definition: game_text.cpp:251
void FreeTranslation()
Free all data related to the translation.
bool FioCheckFileExists(const char *filename, Subdirectory subdir)
Check whether the given file exists.
Definition: fileio.cpp:312
static class GameInfo * GetInfo()
Get the current GameInfo.
Definition: game.hpp:82
char file[MAX_PATH]
Name of the file we read this data from.
Definition: language.h:95
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition: squirrel.hpp:82
void Finalise()
Finalise writing the file.
Definition: game_text.cpp:175
StringList & strings
The encoded strings.
Definition: game_text.cpp:160
virtual void ParseFile()
Start parsing the file.
#define DEBUG(name, level,...)
Output a line of debugging information.
Definition: debug.h:37
const char * _file
The filename of the input, so we can refer to it in errors/warnings.
Definition: strgen_base.cpp:28
GameInfo keeps track of all information of an Game, like Author, Description, ... ...
std::shared_ptr< LanguageStrings > cur_language
The current (compiled) language.
Definition: game_text.hpp:33
Base class for all language writers.
Definition: strgen.h:114
StringList & strings
The string names.
Definition: game_text.cpp:193
std::vector< std::string > StringList
Type for a list of strings.
Definition: string_type.h:60
StringList::const_iterator end
The end of the iteration.
Definition: game_text.cpp:133
std::unique_ptr< LanguageStrings > ReadRawLanguageStrings(const char *file)
Read all the raw language strings from the given file.
Definition: game_text.cpp:84
char * strecpy(char *dst, const char *src, const char *last)
Copies characters from one buffer to another.
Definition: depend.cpp:68
StringNameWriter(StringList &strings)
Writer for the string names.
Definition: game_text.cpp:199
Base functions for all Games.
~LanguageStrings()
Free everything.
Definition: game_text.cpp:74
std::vector< std::unique_ptr< LanguageStrings > > raw_strings
The raw strings per language, first must be English/the master language!.
Definition: game_text.hpp:35
void Write(const byte *buffer, size_t length)
Write a number of bytes.
Definition: game_text.cpp:185
static void free(const void *ptr)
Version of the standard free that accepts const pointers.
Definition: depend.cpp:131
Base class for writing the header, i.e.
Definition: strgen.h:93
Class for writing the string IDs.
Definition: game_text.cpp:192
void Finalise(const StringData &data)
Finalise writing the file.
Definition: game_text.cpp:208
const char * GetGameStringPtr(uint id)
Get the string pointer of a particular game string.
Definition: game_text.cpp:337
const char * language
Name of the language (base filename).
Definition: game_text.hpp:23
StringList string_names
The names of the compiled strings.
Definition: game_text.hpp:37
const char * GetTarFile() const
Get the filename of the tar the script is in.
Definition: script_info.hpp:99
Helper for reading strings.
Definition: strgen.h:62
bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename) override
Add a file with the given filename.
Definition: game_text.cpp:235
void WriteLength(uint length)
Write the length as a simple gamma.
Definition: game_text.cpp:180
void WriteHeader(const StringData &data)
Write the header information.
StringListReader(StringData &data, const LanguageStrings &strings, bool master, bool translation)
Create the reader.
Definition: game_text.cpp:142