OpenTTD Source 20250612-master-gb012d9e3dc
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: {}", _strgen.file, _strgen.cur_line, msg);
29 _strgen.warnings++;
30}
31
32void CDECL StrgenErrorI(const std::string &msg)
33{
34 Debug(script, 0, "{}:{}: error: {}", _strgen.file, _strgen.cur_line, msg);
35 _strgen.errors++;
36}
37
38void CDECL StrgenFatalI(const std::string &msg)
39{
40 Debug(script, 0, "{}:{}: FATAL: {}", _strgen.file, _strgen.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 std::string_view view{buffer};
67 ret.lines.emplace_back(StrTrimView(view, StringConsumer::WHITESPACE_OR_NEWLINE));
68
69 if (view.size() > to_read) {
70 to_read = 0;
71 } else {
72 to_read -= view.size();
73 }
74 }
75
76 return ret;
77}
78
79
82 StringList::const_iterator p;
83 StringList::const_iterator end;
84
93 StringReader(data, strings.language, master, translation), p(strings.lines.begin()), end(strings.lines.end())
94 {
95 }
96
97 std::optional<std::string> ReadLine() override
98 {
99 if (this->p == this->end) return std::nullopt;
100 return *this->p++;
101 }
102};
103
107
115
116 void WriteHeader(const LanguagePackHeader *) override
117 {
118 /* We don't use the header. */
119 }
120
121 void Finalise() override
122 {
123 /* Nothing to do. */
124 }
125
126 void WriteLength(size_t) override
127 {
128 /* We don't write the length. */
129 }
130
131 void Write(std::string_view buffer) override
132 {
133 this->strings.emplace_back(buffer);
134 }
135};
136
140
148
149 void WriteStringID(const std::string &name, size_t stringid) override
150 {
151 if (stringid == this->strings.size()) this->strings.emplace_back(name);
152 }
153
154 void Finalise(const StringData &) override
155 {
156 /* Nothing to do. */
157 }
158};
159
163class LanguageScanner : protected FileScanner {
164private:
165 std::weak_ptr<GameStrings> gs;
166 std::string exclude;
167
168public:
170 LanguageScanner(std::weak_ptr<GameStrings> gs, const std::string &exclude) : gs(gs), exclude(exclude) {}
171
175 void Scan(const std::string &directory)
176 {
177 this->FileScanner::Scan(".txt", directory, false);
178 }
179
180 bool AddFile(const std::string &filename, size_t, const std::string &) override
181 {
182 if (exclude == filename) return true;
183
184 auto ls = ReadRawLanguageStrings(filename);
185 if (!ls.IsValid()) return false;
186
187 if (auto sp = this->gs.lock()) {
188 sp->raw_strings.push_back(std::move(ls));
189 return true;
190 }
191
192 return false;
193 }
194};
195
200static std::shared_ptr<GameStrings> LoadTranslations()
201{
202 const GameInfo *info = Game::GetInfo();
203 assert(info != nullptr);
204 std::string basename(info->GetMainScript());
205 auto e = basename.rfind(PATHSEPCHAR);
206 if (e == std::string::npos) return nullptr;
207 basename.erase(e + 1);
208
209 std::string filename = basename + "lang" PATHSEP "english.txt";
210 if (!FioCheckFileExists(filename, GAME_DIR)) return nullptr;
211
212 auto ls = ReadRawLanguageStrings(filename);
213 if (!ls.IsValid()) return nullptr;
214
215 auto gs = std::make_shared<GameStrings>();
216 try {
217 gs->raw_strings.push_back(std::move(ls));
218
219 /* Scan for other language files */
220 LanguageScanner scanner(gs, filename);
221 std::string ldir = basename + "lang" PATHSEP;
222
223 const std::string tar_filename = info->GetTarFile();
224 TarList::iterator iter;
225 if (!tar_filename.empty() && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
226 /* The main script is in a tar file, so find all files that
227 * are in the same tar and add them to the langfile scanner. */
228 for (const auto &tar : _tar_filelist[GAME_DIR]) {
229 /* Not in the same tar. */
230 if (tar.second.tar_filename != iter->first) continue;
231
232 /* Check the path and extension. */
233 if (tar.first.size() <= ldir.size() || tar.first.compare(0, ldir.size(), ldir) != 0) continue;
234 if (tar.first.compare(tar.first.size() - 4, 4, ".txt") != 0) continue;
235
236 scanner.AddFile(tar.first, 0, tar_filename);
237 }
238 } else {
239 /* Scan filesystem */
240 scanner.Scan(ldir);
241 }
242
243 gs->Compile();
244 return gs;
245 } catch (...) {
246 return nullptr;
247 }
248}
249
250static StringParam::ParamType GetParamType(const CmdStruct *cs)
251{
252 if (cs->value == SCC_RAW_STRING_POINTER) return StringParam::RAW_STRING;
253 if (cs->value == SCC_STRING || cs != TranslateCmdForCompare(cs)) return StringParam::STRING;
254 return StringParam::OTHER;
255}
256
257static void ExtractStringParams(const StringData &data, StringParamsList &params)
258{
259 for (size_t i = 0; i < data.max_strings; i++) {
260 const LangString *ls = data.strings[i].get();
261
262 if (ls != nullptr) {
263 StringParams &param = params.emplace_back();
264 ParsedCommandStruct pcs = ExtractCommandString(ls->english, false);
265
266 for (auto it = pcs.consuming_commands.begin(); it != pcs.consuming_commands.end(); it++) {
267 if (*it == nullptr) {
268 /* Skip empty param unless a non empty param exist after it. */
269 if (std::all_of(it, pcs.consuming_commands.end(), [](auto cs) { return cs == nullptr; })) break;
270 param.emplace_back(StringParam::UNUSED, 1);
271 continue;
272 }
273 const CmdStruct *cs = *it;
274 param.emplace_back(GetParamType(cs), cs->consumes, cs->cmd);
275 }
276 }
277 }
278}
279
282{
283 StringData data(32);
284 StringListReader master_reader(data, this->raw_strings[0], true, false);
285 master_reader.ParseFile();
286 if (_strgen.errors != 0) throw std::exception();
287
288 this->version = data.Version();
289
290 ExtractStringParams(data, this->string_params);
291
292 StringNameWriter id_writer(this->string_names);
293 id_writer.WriteHeader(data);
294
295 for (const auto &p : this->raw_strings) {
296 data.FreeTranslation();
297 StringListReader translation_reader(data, p, false, p.language != "english");
298 translation_reader.ParseFile();
299 if (_strgen.errors != 0) throw std::exception();
300
301 auto &strings = this->compiled_strings.emplace_back(p.language);
302 TranslationWriter writer(strings.lines);
303 writer.WriteLang(data);
304 }
305}
306
308std::shared_ptr<GameStrings> _current_gamestrings_data = nullptr;
309
316{
317 if (_current_gamestrings_data == nullptr || _current_gamestrings_data->cur_language == nullptr || id.base() >= _current_gamestrings_data->cur_language->lines.size()) return GetStringPtr(STR_UNDEFINED);
318 return _current_gamestrings_data->cur_language->lines[id];
319}
320
327{
328 /* An empty result for STR_UNDEFINED. */
329 static StringParams empty;
330
331 if (id.base() >= _current_gamestrings_data->string_params.size()) return empty;
332 return _current_gamestrings_data->string_params[id];
333}
334
341{
342 /* The name for STR_UNDEFINED. */
343 static const std::string undefined = "STR_UNDEFINED";
344
345 if (id.base() >= _current_gamestrings_data->string_names.size()) return undefined;
346 return _current_gamestrings_data->string_names[id];
347}
348
354{
356 if (_current_gamestrings_data == nullptr) return;
357
358 HSQUIRRELVM vm = engine.GetVM();
359 sq_pushroottable(vm);
360 sq_pushstring(vm, "GSText");
361 if (SQ_FAILED(sq_get(vm, -2))) return;
362
363 int idx = 0;
364 for (const auto &p : _current_gamestrings_data->string_names) {
365 sq_pushstring(vm, p);
366 sq_pushinteger(vm, idx);
367 sq_rawset(vm, -3);
368 idx++;
369 }
370
371 sq_pop(vm, 2);
372
373 ScriptText::SetPadParameterCount(vm);
374
376}
377
382{
383 if (_current_gamestrings_data == nullptr) return;
384
385 std::string language = FS2OTTD(_current_language->file.stem().native());
386 for (auto &p : _current_gamestrings_data->compiled_strings) {
387 if (p.language == language) {
388 _current_gamestrings_data->cur_language = &p;
389 return;
390 }
391 }
392
393 _current_gamestrings_data->cur_language = &_current_gamestrings_data->compiled_strings[0];
394}
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:1112
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:70
Scanner to find language files in a GameScript directory.
LanguageScanner(std::weak_ptr< GameStrings > gs, const std::string &exclude)
Initialise.
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.
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:82
static const std::string_view WHITESPACE_OR_NEWLINE
ASCII whitespace characters, including new-line.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition fileio.cpp:242
bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
Check whether the given file exists.
Definition fileio.cpp:121
@ 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(StringIndexInTab id)
Get the name of a particular game string.
static std::shared_ptr< GameStrings > LoadTranslations()
Load all translations that we know of.
const StringParams & GetGameStringParams(StringIndexInTab id)
Get the string parameters of a particular game string.
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
std::string_view GetGameStringPtr(StringIndexInTab id)
Get the string pointer of a particular game string.
std::shared_ptr< GameStrings > _current_gamestrings_data
The currently loaded game strings.
Base functions regarding game texts.
const LanguageMetadata * _current_language
The currently loaded language.
Definition strings.cpp:55
std::vector< std::string > StringList
Type for a list of strings.
Definition string_type.h:60
ReferenceThroughBaseContainer< StringList > string_names
The names of the compiled strings.
Definition game_text.hpp:59
std::vector< LanguageStrings > raw_strings
The raw strings per language, first must be English/the master language!.
Definition game_text.hpp:57
std::vector< LanguageStrings > compiled_strings
The compiled strings per language, first must be English/the master language!.
Definition game_text.hpp:58
uint version
The version of the language strings.
Definition game_text.hpp:54
ReferenceThroughBaseContainer< StringParamsList > string_params
The parameters for the strings.
Definition game_text.hpp:60
void Compile()
Compile the language.
Base class for writing the header, i.e.
Definition strgen.h:88
void WriteHeader(const StringData &data)
Write the header information.
Information about a single string.
Definition strgen.h:30
std::string english
English text.
Definition strgen.h:32
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:40
ReferenceThroughBaseContainer< StringList > lines
The lines of the file to pass into the parser/encoder.
Definition game_text.hpp:42
Base class for all language writers.
Definition strgen.h:109
virtual void WriteLang(const StringData &data)
Actually write the language.
std::string file
The filename of the input, so we can refer to it in errors/warnings.
Definition strgen.h:160
size_t cur_line
The current line we're parsing in the input file.
Definition strgen.h:161
Information about the currently known strings.
Definition strgen.h:43
size_t max_strings
The maximum number of strings.
Definition strgen.h:47
void FreeTranslation()
Free all data related to the translation.
std::vector< std::shared_ptr< LangString > > strings
List of all known strings.
Definition strgen.h:44
uint32_t Version() const
Make a hash of the file to get a unique "version number".
A reader that simply reads using fopen.
Definition game_text.cpp:81
StringList::const_iterator p
The current location of the iteration.
Definition game_text.cpp:82
StringList::const_iterator end
The end of the iteration.
Definition game_text.cpp:83
StringListReader(StringData &data, const LanguageStrings &strings, bool master, bool translation)
Create the reader.
Definition game_text.cpp:92
std::optional< std::string > ReadLine() override
Read a single line from the source of strings.
Definition game_text.cpp:97
Class for writing the string IDs.
void WriteStringID(const std::string &name, size_t stringid) override
Write the string ID.
void Finalise(const StringData &) override
Finalise writing the file.
StringNameWriter(StringList &strings)
Writer for the string names.
StringList & strings
The string names.
Helper for reading strings.
Definition strgen.h:59
StringData & data
The data to fill during reading.
Definition strgen.h:60
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:63
bool master
Are we reading the master file?
Definition strgen.h:62
Templated helper to make a type-safe 'typedef' representing a single POD value.
Class for writing an encoded language.
void Write(std::string_view buffer) override
Write a number of bytes.
void WriteLength(size_t) 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 Finalise() override
Finalise writing the file.
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
Definition win32.cpp:337