OpenTTD Source  20241121-master-g67a0fccfad
game_text.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of OpenTTD.
3  * 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.
4  * 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.
5  * 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/>.
6  */
7 
10 #include "../stdafx.h"
11 #include "../strgen/strgen.h"
12 #include "../debug.h"
13 #include "../fileio_func.h"
14 #include "../tar_type.h"
15 #include "../script/squirrel_class.hpp"
16 #include "../strings_func.h"
17 #include "game_text.hpp"
18 #include "game.hpp"
19 #include "game_info.hpp"
20 
21 #include "table/strings.h"
22 #include "table/strgen_tables.h"
23 
24 #include "../safeguards.h"
25 
26 void CDECL StrgenWarningI(const std::string &msg)
27 {
28  Debug(script, 0, "{}:{}: warning: {}", _file, _cur_line, msg);
29  _warnings++;
30 }
31 
32 void CDECL StrgenErrorI(const std::string &msg)
33 {
34  Debug(script, 0, "{}:{}: error: {}", _file, _cur_line, msg);
35  _errors++;
36 }
37 
38 void CDECL StrgenFatalI(const std::string &msg)
39 {
40  Debug(script, 0, "{}:{}: FATAL: {}", _file, _cur_line, msg);
41  throw std::exception();
42 }
43 
49 LanguageStrings ReadRawLanguageStrings(const std::string &file)
50 {
51  size_t to_read;
52  auto fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
53  if (!fh.has_value()) return LanguageStrings();
54 
55  auto pos = file.rfind(PATHSEPCHAR);
56  if (pos == std::string::npos) return LanguageStrings();
57  std::string langname = file.substr(pos + 1);
58 
59  /* Check for invalid empty filename */
60  if (langname.empty() || langname.front() == '.') return LanguageStrings();
61 
62  LanguageStrings ret(langname.substr(0, langname.find('.')));
63 
64  char buffer[2048];
65  while (to_read != 0 && fgets(buffer, sizeof(buffer), *fh) != nullptr) {
66  size_t len = strlen(buffer);
67 
68  /* Remove trailing spaces/newlines from the string. */
69  size_t i = len;
70  while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
71  buffer[i] = '\0';
72 
73  ret.lines.emplace_back(buffer, i);
74 
75  if (len > to_read) {
76  to_read = 0;
77  } else {
78  to_read -= len;
79  }
80  }
81 
82  return ret;
83 }
84 
85 
88  StringList::const_iterator p;
89  StringList::const_iterator end;
90 
99  StringReader(data, strings.language.c_str(), master, translation), p(strings.lines.begin()), end(strings.lines.end())
100  {
101  }
102 
103  std::optional<std::string> ReadLine() override
104  {
105  if (this->p == this->end) return std::nullopt;
106  return *this->p++;
107  }
108 };
109 
113 
119  {
120  }
121 
122  void WriteHeader(const LanguagePackHeader *) override
123  {
124  /* We don't use the header. */
125  }
126 
127  void Finalise() override
128  {
129  /* Nothing to do. */
130  }
131 
132  void WriteLength(uint) override
133  {
134  /* We don't write the length. */
135  }
136 
137  void Write(const uint8_t *buffer, size_t length) override
138  {
139  this->strings.emplace_back((const char *)buffer, length);
140  }
141 };
142 
146 
152  {
153  }
154 
155  void WriteStringID(const std::string &name, int stringid) override
156  {
157  if (stringid == (int)this->strings.size()) this->strings.emplace_back(name);
158  }
159 
160  void Finalise(const StringData &) override
161  {
162  /* Nothing to do. */
163  }
164 };
165 
169 class LanguageScanner : protected FileScanner {
170 private:
171  GameStrings *gs;
172  std::string exclude;
173 
174 public:
176  LanguageScanner(GameStrings *gs, const std::string &exclude) : gs(gs), exclude(exclude) {}
177 
181  void Scan(const std::string &directory)
182  {
183  this->FileScanner::Scan(".txt", directory, false);
184  }
185 
186  bool AddFile(const std::string &filename, size_t, const std::string &) override
187  {
188  if (exclude == filename) return true;
189 
190  auto ls = ReadRawLanguageStrings(filename);
191  if (!ls.IsValid()) return false;
192 
193  gs->raw_strings.push_back(std::move(ls));
194  return true;
195  }
196 };
197 
203 {
204  const GameInfo *info = Game::GetInfo();
205  assert(info != nullptr);
206  std::string basename(info->GetMainScript());
207  auto e = basename.rfind(PATHSEPCHAR);
208  if (e == std::string::npos) return nullptr;
209  basename.erase(e + 1);
210 
211  std::string filename = basename + "lang" PATHSEP "english.txt";
212  if (!FioCheckFileExists(filename, GAME_DIR)) return nullptr;
213 
214  auto ls = ReadRawLanguageStrings(filename);
215  if (!ls.IsValid()) return nullptr;
216 
217  GameStrings *gs = new GameStrings();
218  try {
219  gs->raw_strings.push_back(std::move(ls));
220 
221  /* Scan for other language files */
222  LanguageScanner scanner(gs, filename);
223  std::string ldir = basename + "lang" PATHSEP;
224 
225  const std::string tar_filename = info->GetTarFile();
226  TarList::iterator iter;
227  if (!tar_filename.empty() && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
228  /* The main script is in a tar file, so find all files that
229  * are in the same tar and add them to the langfile scanner. */
230  for (const auto &tar : _tar_filelist[GAME_DIR]) {
231  /* Not in the same tar. */
232  if (tar.second.tar_filename != iter->first) continue;
233 
234  /* Check the path and extension. */
235  if (tar.first.size() <= ldir.size() || tar.first.compare(0, ldir.size(), ldir) != 0) continue;
236  if (tar.first.compare(tar.first.size() - 4, 4, ".txt") != 0) continue;
237 
238  scanner.AddFile(tar.first, 0, tar_filename);
239  }
240  } else {
241  /* Scan filesystem */
242  scanner.Scan(ldir);
243  }
244 
245  gs->Compile();
246  return gs;
247  } catch (...) {
248  delete gs;
249  return nullptr;
250  }
251 }
252 
253 static StringParam::ParamType GetParamType(const CmdStruct *cs)
254 {
255  if (cs->value == SCC_RAW_STRING_POINTER) return StringParam::RAW_STRING;
256  if (cs->value == SCC_STRING || cs != TranslateCmdForCompare(cs)) return StringParam::STRING;
257  return StringParam::OTHER;
258 }
259 
260 static void ExtractStringParams(const StringData &data, StringParamsList &params)
261 {
262  for (size_t i = 0; i < data.max_strings; i++) {
263  const LangString *ls = data.strings[i].get();
264 
265  if (ls != nullptr) {
266  StringParams &param = params.emplace_back();
267  ParsedCommandStruct pcs = ExtractCommandString(ls->english.c_str(), false);
268 
269  for (auto it = pcs.consuming_commands.begin(); it != pcs.consuming_commands.end(); it++) {
270  if (*it == nullptr) {
271  /* Skip empty param unless a non empty param exist after it. */
272  if (std::all_of(it, pcs.consuming_commands.end(), [](auto cs) { return cs == nullptr; })) break;
273  param.emplace_back(StringParam::UNUSED, 1, nullptr);
274  continue;
275  }
276  const CmdStruct *cs = *it;
277  param.emplace_back(GetParamType(cs), cs->consumes, cs->cmd);
278  }
279  }
280  }
281 }
282 
285 {
286  StringData data(32);
287  StringListReader master_reader(data, this->raw_strings[0], true, false);
288  master_reader.ParseFile();
289  if (_errors != 0) throw std::exception();
290 
291  this->version = data.Version();
292 
293  ExtractStringParams(data, this->string_params);
294 
295  StringNameWriter id_writer(this->string_names);
296  id_writer.WriteHeader(data);
297 
298  for (const auto &p : this->raw_strings) {
299  data.FreeTranslation();
300  StringListReader translation_reader(data, p, false, p.language != "english");
301  translation_reader.ParseFile();
302  if (_errors != 0) throw std::exception();
303 
304  this->compiled_strings.emplace_back(p.language);
305  TranslationWriter writer(this->compiled_strings.back().lines);
306  writer.WriteLang(data);
307  }
308 }
309 
312 
318 const char *GetGameStringPtr(uint id)
319 {
320  if (_current_data == nullptr || _current_data->cur_language == nullptr || id >= _current_data->cur_language->lines.size()) return GetStringPtr(STR_UNDEFINED);
321  return _current_data->cur_language->lines[id].c_str();
322 }
323 
329 const StringParams &GetGameStringParams(uint id)
330 {
331  /* An empty result for STR_UNDEFINED. */
332  static StringParams empty;
333 
334  if (id >= _current_data->string_params.size()) return empty;
335  return _current_data->string_params[id];
336 }
337 
343 const std::string &GetGameStringName(uint id)
344 {
345  /* The name for STR_UNDEFINED. */
346  static const std::string undefined = "STR_UNDEFINED";
347 
348  if (id >= _current_data->string_names.size()) return undefined;
349  return _current_data->string_names[id];
350 }
351 
357 {
358  delete _current_data;
360  if (_current_data == nullptr) return;
361 
362  HSQUIRRELVM vm = engine->GetVM();
363  sq_pushroottable(vm);
364  sq_pushstring(vm, "GSText", -1);
365  if (SQ_FAILED(sq_get(vm, -2))) return;
366 
367  int idx = 0;
368  for (const auto &p : _current_data->string_names) {
369  sq_pushstring(vm, p, -1);
370  sq_pushinteger(vm, idx);
371  sq_rawset(vm, -3);
372  idx++;
373  }
374 
375  sq_pop(vm, 2);
376 
378 }
379 
384 {
385  if (_current_data == nullptr) return;
386 
387  std::string language = FS2OTTD(_current_language->file.stem());
388  for (auto &p : _current_data->compiled_strings) {
389  if (p.language == language) {
391  return;
392  }
393  }
394 
396 }
Helper for scanning for files with a given name.
Definition: fileio_func.h:37
uint Scan(std::string_view extension, Subdirectory sd, bool tars=true, bool recursive=true)
Scan for files with the given extension in the given search path.
Definition: fileio.cpp:1114
All static information from an Game like name, version, etc.
Definition: game_info.hpp:16
static class GameInfo * GetInfo()
Get the current GameInfo.
Definition: game.hpp:75
Scanner to find language files in a GameScript directory.
Definition: game_text.cpp:169
void Scan(const std::string &directory)
Scan.
Definition: game_text.cpp:181
bool AddFile(const std::string &filename, size_t, const std::string &) override
Add a file with the given filename.
Definition: game_text.cpp:186
LanguageScanner(GameStrings *gs, const std::string &exclude)
Initialise.
Definition: game_text.cpp:176
const std::string & GetTarFile() const
Get the filename of the tar the script is in.
Definition: script_info.hpp:86
const std::string & GetMainScript() const
Get the filename of the main.nut script.
Definition: script_info.hpp:81
HSQUIRRELVM GetVM()
Get the squirrel VM.
Definition: squirrel.hpp:80
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
bool FioCheckFileExists(const std::string &filename, Subdirectory subdir)
Check whether the given file exists.
Definition: fileio.cpp:121
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition: fileio.cpp:242
@ GAME_DIR
Subdirectory for all game scripts.
Definition: fileio_type.h:128
Base functions for all Games.
GameInfo keeps track of all information of an Game, like Author, Description, ...
void RegisterGameTranslation(Squirrel *engine)
Register the current translation to the Squirrel engine.
Definition: game_text.cpp:356
const StringParams & GetGameStringParams(uint id)
Get the string parameters of a particular game string.
Definition: game_text.cpp:329
GameStrings * _current_data
The currently loaded game strings.
Definition: game_text.cpp:311
void ReconsiderGameScriptLanguage()
Reconsider the game script language, so we use the right one.
Definition: game_text.cpp:383
const char * GetGameStringPtr(uint id)
Get the string pointer of a particular game string.
Definition: game_text.cpp:318
LanguageStrings ReadRawLanguageStrings(const std::string &file)
Read all the raw language strings from the given file.
Definition: game_text.cpp:49
const std::string & GetGameStringName(uint id)
Get the name of a particular game string.
Definition: game_text.cpp:343
GameStrings * LoadTranslations()
Load all translations that we know of.
Definition: game_text.cpp:202
Base functions regarding game texts.
const LanguageMetadata * _current_language
The currently loaded language.
Definition: strings.cpp:54
int _cur_line
The current line we're parsing in the input file.
Definition: strgen_base.cpp:30
const char * _file
The filename of the input, so we can refer to it in errors/warnings.
Definition: strgen_base.cpp:29
Tables of commands for strgen.
std::vector< std::string > StringList
Type for a list of strings.
Definition: string_type.h:60
Container for all the game strings.
Definition: game_text.hpp:50
std::vector< LanguageStrings > raw_strings
The raw strings per language, first must be English/the master language!.
Definition: game_text.hpp:54
std::vector< LanguageStrings > compiled_strings
The compiled strings per language, first must be English/the master language!.
Definition: game_text.hpp:55
uint version
The version of the language strings.
Definition: game_text.hpp:51
StringList string_names
The names of the compiled strings.
Definition: game_text.hpp:56
void Compile()
Compile the language.
Definition: game_text.cpp:284
StringParamsList string_params
The parameters for the strings.
Definition: game_text.hpp:57
LanguageStrings * cur_language
The current (compiled) language.
Definition: game_text.hpp:52
Base class for writing the header, i.e.
Definition: strgen.h:87
void WriteHeader(const StringData &data)
Write the header information.
Information about a single string.
Definition: strgen.h:28
std::string english
English text.
Definition: strgen.h:30
std::filesystem::path file
Name of the file we read this data from.
Definition: language.h:94
Header of a language file.
Definition: language.h:24
Container for the raw (unencoded) language strings of a language.
Definition: game_text.hpp:37
StringList lines
The lines of the file to pass into the parser/encoder.
Definition: game_text.hpp:39
Base class for all language writers.
Definition: strgen.h:108
virtual void WriteLang(const StringData &data)
Actually write the language.
Information about the currently known strings.
Definition: strgen.h:41
std::vector< std::unique_ptr< LangString > > strings
List of all known strings.
Definition: strgen.h:42
size_t max_strings
The maximum number of strings.
Definition: strgen.h:45
uint Version() const
Make a hash of the file to get a unique "version number".
void FreeTranslation()
Free all data related to the translation.
Definition: strgen_base.cpp:76
A reader that simply reads using fopen.
Definition: game_text.cpp:87
StringList::const_iterator p
The current location of the iteration.
Definition: game_text.cpp:88
StringList::const_iterator end
The end of the iteration.
Definition: game_text.cpp:89
std::optional< std::string > ReadLine() override
Read a single line from the source of strings.
Definition: game_text.cpp:103
StringListReader(StringData &data, const LanguageStrings &strings, bool master, bool translation)
Create the reader.
Definition: game_text.cpp:98
Class for writing the string IDs.
Definition: game_text.cpp:144
void Finalise(const StringData &) override
Finalise writing the file.
Definition: game_text.cpp:160
void WriteStringID(const std::string &name, int stringid) override
Write the string ID.
Definition: game_text.cpp:155
StringNameWriter(StringList &strings)
Writer for the string names.
Definition: game_text.cpp:151
StringList & strings
The string names.
Definition: game_text.cpp:145
Helper for reading strings.
Definition: strgen.h:58
StringData & data
The data to fill during reading.
Definition: strgen.h:59
virtual void ParseFile()
Start parsing the file.
bool translation
Are we reading a translation, implies !master. However, the base translation will have this false.
Definition: strgen.h:62
bool master
Are we reading the master file?
Definition: strgen.h:61
Class for writing an encoded language.
Definition: game_text.cpp:111
void WriteLength(uint) override
Write the length as a simple gamma.
Definition: game_text.cpp:132
StringList & strings
The encoded strings.
Definition: game_text.cpp:112
void WriteHeader(const LanguagePackHeader *) override
Write the header metadata.
Definition: game_text.cpp:122
TranslationWriter(StringList &strings)
Writer for the encoded data.
Definition: game_text.cpp:118
void Write(const uint8_t *buffer, size_t length) override
Write a number of bytes.
Definition: game_text.cpp:137
void Finalise() override
Finalise writing the file.
Definition: game_text.cpp:127
std::string FS2OTTD(const std::wstring &name)
Convert to OpenTTD's encoding from a wide string.
Definition: win32.cpp:337