OpenTTD Source 20241224-master-gf74b0cf984
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
26void CDECL StrgenWarningI(const std::string &msg)
27{
28 Debug(script, 0, "{}:{}: warning: {}", _file, _cur_line, msg);
29 _warnings++;
30}
31
32void CDECL StrgenErrorI(const std::string &msg)
33{
34 Debug(script, 0, "{}:{}: error: {}", _file, _cur_line, msg);
35 _errors++;
36}
37
38void CDECL StrgenFatalI(const std::string &msg)
39{
40 Debug(script, 0, "{}:{}: FATAL: {}", _file, _cur_line, msg);
41 throw std::exception();
42}
43
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
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
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
169class LanguageScanner : protected FileScanner {
170private:
171 GameStrings *gs;
172 std::string exclude;
173
174public:
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
253static 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
260static 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
318const 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
329const 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
343const 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.
void Scan(const std::string &directory)
Scan.
bool AddFile(const std::string &filename, size_t, const std::string &) override
Add a file with the given filename.
LanguageScanner(GameStrings *gs, const std::string &exclude)
Initialise.
const std::string & GetMainScript() const
Get the filename of the main.nut script.
const std::string & GetTarFile() const
Get the filename of the tar the script is in.
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.
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.
const std::string & GetGameStringName(uint id)
Get the name of a particular game string.
GameStrings * LoadTranslations()
Load all translations that we know of.
GameStrings * _current_data
The currently loaded game strings.
void ReconsiderGameScriptLanguage()
Reconsider the game script language, so we use the right one.
LanguageStrings ReadRawLanguageStrings(const std::string &file)
Read all the raw language strings from the given file.
Definition game_text.cpp:49
const char * GetGameStringPtr(uint id)
Get the string pointer of a particular game string.
const StringParams & GetGameStringParams(uint id)
Get the string parameters of a particular game string.
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.
const char * _file
The filename of the input, so we can refer to it in errors/warnings.
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.
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.
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
StringListReader(StringData &data, const LanguageStrings &strings, bool master, bool translation)
Create the reader.
Definition game_text.cpp:98
std::optional< std::string > ReadLine() override
Read a single line from the source of strings.
Class for writing the string IDs.
void Finalise(const StringData &) override
Finalise writing the file.
void WriteStringID(const std::string &name, int stringid) override
Write the string ID.
StringNameWriter(StringList &strings)
Writer for the string names.
StringList & strings
The string names.
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.
void WriteLength(uint) override
Write the length as a simple gamma.
StringList & strings
The encoded strings.
void WriteHeader(const LanguagePackHeader *) override
Write the header metadata.
TranslationWriter(StringList &strings)
Writer for the encoded data.
void Write(const uint8_t *buffer, size_t length) override
Write a number of bytes.
void Finalise() override
Finalise writing the file.
std::string FS2OTTD(const std::wstring &name)
Convert to OpenTTD's encoding from a wide string.
Definition win32.cpp:337