OpenTTD Source: src/game/game_text.cpp Source File

OpenTTD Source 14.0-beta3

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