OpenTTD Source 20260731-master-g77ba2b244a
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
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/api/script_text.hpp"
17#include "../strings_func.h"
18#include "game_text.hpp"
19#include "game.hpp"
20#include "game_info.hpp"
21
22#include "table/strings.h"
25
26#include "../safeguards.h"
27
28void CDECL StrgenWarningI(const std::string &msg)
29{
30 Debug(script, 0, "{}:{}: warning: {}", _strgen.file, _strgen.cur_line, msg);
31 _strgen.warnings++;
32}
33
34void CDECL StrgenErrorI(const std::string &msg)
35{
36 Debug(script, 0, "{}:{}: error: {}", _strgen.file, _strgen.cur_line, msg);
37 _strgen.errors++;
38}
39
40void CDECL StrgenFatalI(const std::string &msg)
41{
42 Debug(script, 0, "{}:{}: FATAL: {}", _strgen.file, _strgen.cur_line, msg);
43 throw std::exception();
44}
45
52{
53 size_t to_read;
54 auto fh = FioFOpenFile(file, "rb", Subdirectory::Gs, &to_read);
55 if (!fh.has_value()) return LanguageStrings();
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 std::string_view view{buffer};
69 ret.lines.emplace_back(StrTrimView(view, StringConsumer::WHITESPACE_OR_NEWLINE));
70
71 if (view.size() > to_read) {
72 to_read = 0;
73 } else {
74 to_read -= view.size();
75 }
76 }
77
78 return ret;
79}
80
81
84 StringList::const_iterator p;
85 StringList::const_iterator end;
86
95 StringReader(data, strings.language, master, translation), p(strings.lines.begin()), end(strings.lines.end())
96 {
97 }
98
99 std::optional<std::string> ReadLine() override
100 {
101 if (this->p == this->end) return std::nullopt;
102 return *this->p++;
103 }
104};
105
109
117
118 void WriteHeader(const LanguagePackHeader *) override
119 {
120 /* We don't use the header. */
121 }
122
123 void Finalise() override
124 {
125 /* Nothing to do. */
126 }
127
128 void WriteLength(size_t) override
129 {
130 /* We don't write the length. */
131 }
132
133 void Write(std::string_view buffer) override
134 {
135 this->strings.emplace_back(buffer);
136 }
137};
138
142
150
151 void WriteStringID(const std::string &name, size_t stringid) override
152 {
153 if (stringid == this->strings.size()) this->strings.emplace_back(name);
154 }
155
156 void Finalise(const StringData &) override
157 {
158 /* Nothing to do. */
159 }
160};
161
165class LanguageScanner : protected FileScanner {
166private:
167 std::weak_ptr<GameStrings> gs;
168 std::string exclude;
169
170public:
176 LanguageScanner(std::weak_ptr<GameStrings> gs, const std::string &exclude) : gs(gs), exclude(exclude) {}
177
182 void Scan(const std::string &directory)
183 {
184 this->FileScanner::Scan(".txt", directory, false);
185 }
186
187 bool AddFile(const std::string &filename, size_t, const std::string &) override
188 {
189 if (exclude == filename) return true;
190
191 auto ls = ReadRawLanguageStrings(filename);
192 if (!ls.IsValid()) return false;
193
194 if (auto sp = this->gs.lock()) {
195 sp->raw_strings.push_back(std::move(ls));
196 return true;
197 }
198
199 return false;
200 }
201};
202
207static std::shared_ptr<GameStrings> LoadTranslations()
208{
209 const GameInfo *info = Game::GetInfo();
210 assert(info != nullptr);
211 std::string basename(info->GetMainScript());
212 auto e = basename.rfind(PATHSEPCHAR);
213 if (e == std::string::npos) return nullptr;
214 basename.erase(e + 1);
215
216 std::string filename = basename + "lang" PATHSEP "english.txt";
217 if (!FioCheckFileExists(filename, Subdirectory::Gs)) return nullptr;
218
219 auto ls = ReadRawLanguageStrings(filename);
220 if (!ls.IsValid()) return nullptr;
221
222 auto gs = std::make_shared<GameStrings>();
223 try {
224 gs->raw_strings.push_back(std::move(ls));
225
226 /* Scan for other language files */
227 LanguageScanner scanner(gs, filename);
228 std::string ldir = basename + "lang" PATHSEP;
229
230 const std::string tar_filename = info->GetTarFile();
231 TarList::iterator iter;
232 if (!tar_filename.empty() && (iter = _tar_list[Subdirectory::Gs].find(tar_filename)) != _tar_list[Subdirectory::Gs].end()) {
233 /* The main script is in a tar file, so find all files that
234 * are in the same tar and add them to the langfile scanner. */
235 for (const auto &[name, entry] : _tar_filelist[Subdirectory::Gs]) {
236 /* Not in the same tar. */
237 if (entry.tar_filename != iter->first) continue;
238
239 /* Check the path and extension. */
240 if (!name.starts_with(ldir)) continue;
241 if (!name.ends_with(".txt")) continue;
242
243 scanner.AddFile(name, 0, tar_filename);
244 }
245 } else {
246 /* Scan filesystem */
247 scanner.Scan(ldir);
248 }
249
250 gs->Compile();
251 return gs;
252 } catch (...) {
253 return nullptr;
254 }
255}
256
257static StringParam::ParamType GetParamType(const CmdStruct *cs)
258{
259 if (cs->value == SCC_RAW_STRING_POINTER) return StringParam::RAW_STRING;
260 if (cs->value == SCC_STRING || cs != TranslateCmdForCompare(cs)) return StringParam::STRING;
261 return StringParam::OTHER;
262}
263
264static void ExtractStringParams(const StringData &data, StringParamsList &params)
265{
266 for (size_t i = 0; i < data.max_strings; i++) {
267 const LangString *ls = data.strings[i].get();
268
269 if (ls != nullptr) {
270 StringParams &param = params.emplace_back();
271 ParsedCommandStruct pcs = ExtractCommandString(ls->english, false);
272
273 for (auto it = pcs.consuming_commands.begin(); it != pcs.consuming_commands.end(); it++) {
274 if (*it == nullptr) {
275 /* Skip empty param unless a non empty param exist after it. */
276 if (std::all_of(it, pcs.consuming_commands.end(), [](auto cs) { return cs == nullptr; })) break;
277 param.emplace_back(StringParam::UNUSED, 1);
278 continue;
279 }
280 const CmdStruct *cs = *it;
281 param.emplace_back(GetParamType(cs), cs->consumes, cs->cmd);
282 }
283 }
284 }
285}
286
289{
290 StringData data(32);
291 StringListReader master_reader(data, this->raw_strings[0], true, false);
292 master_reader.ParseFile();
293 if (_strgen.errors != 0) throw std::exception();
294
295 this->version = data.Version();
296
297 ExtractStringParams(data, this->string_params);
298
299 StringNameWriter id_writer(this->string_names);
300 id_writer.WriteHeader(data);
301
302 for (const auto &p : this->raw_strings) {
303 data.FreeTranslation();
304 StringListReader translation_reader(data, p, false, p.language != "english");
305 translation_reader.ParseFile();
306 if (_strgen.errors != 0) throw std::exception();
307
308 auto &strings = this->compiled_strings.emplace_back(p.language);
309 TranslationWriter writer(strings.lines);
310 writer.WriteLang(data);
311 }
312}
313
315std::shared_ptr<GameStrings> _current_gamestrings_data = nullptr;
316
323{
324 if (_current_gamestrings_data == nullptr || _current_gamestrings_data->cur_language == nullptr || id.base() >= _current_gamestrings_data->cur_language->lines.size()) return GetStringPtr(STR_UNDEFINED);
325 return _current_gamestrings_data->cur_language->lines[id];
326}
327
334{
335 /* An empty result for STR_UNDEFINED. */
336 static StringParams empty;
337
338 if (id.base() >= _current_gamestrings_data->string_params.size()) return empty;
339 return _current_gamestrings_data->string_params[id];
340}
341
348{
349 /* The name for STR_UNDEFINED. */
350 static const std::string undefined = "STR_UNDEFINED";
351
352 if (id.base() >= _current_gamestrings_data->string_names.size()) return undefined;
353 return _current_gamestrings_data->string_names[id];
354}
355
361{
363 if (_current_gamestrings_data == nullptr) return;
364
365 HSQUIRRELVM vm = engine.GetVM();
366 sq_pushroottable(vm);
367 sq_pushstring(vm, "GSText");
368 if (SQ_FAILED(sq_get(vm, -2))) return;
369
370 int idx = 0;
371 for (const auto &p : _current_gamestrings_data->string_names) {
372 sq_pushstring(vm, p);
373 sq_pushinteger(vm, idx);
374 sq_rawset(vm, -3);
375 idx++;
376 }
377
378 sq_pop(vm, 2);
379
380 ScriptText::SetPadParameterCount(vm);
381
383}
384
389{
390 if (_current_gamestrings_data == nullptr) return;
391
392 std::string language = FS2OTTD(_current_language->file.stem().native());
393 for (auto &p : _current_gamestrings_data->compiled_strings) {
394 if (p.language == language) {
395 _current_gamestrings_data->cur_language = &p;
396 return;
397 }
398 }
399
400 _current_gamestrings_data->cur_language = &_current_gamestrings_data->compiled_strings[0];
401}
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:1170
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:72
Scanner to find language files in a GameScript directory.
LanguageScanner(std::weak_ptr< GameStrings > gs, const std::string &exclude)
Initialise the scanner.
void Scan(const std::string &directory)
Actually run the scan.
std::weak_ptr< GameStrings > gs
The (already) loaded game strings.
std::string exclude
The file name to exclude during scanning.
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:97
static const std::string_view WHITESPACE_OR_NEWLINE
ASCII whitespace characters, including new-line.
Control codes that are embedded in the translation strings.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
EnumIndexArray< TarFileList, Subdirectory, Subdirectory::End > _tar_filelist
List of files within tar files found in each subdirectory.
Definition fileio.cpp:70
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:249
EnumIndexArray< TarList, Subdirectory, Subdirectory::End > _tar_list
List of tar files found in each subdirectory.
Definition fileio.cpp:68
bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
Check whether the given file exists.
Definition fileio.cpp:123
Functions for standard in/out file operations.
@ Gs
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:51
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:54
A number of safeguards to prevent using unsafe methods.
Defines templates for converting C++ classes to Squirrel classes.
Definition of base types and functions in a cross-platform compatible way.
Structures related to strgen.
Tables of commands for strgen.
std::vector< std::string > StringList
Type for a list of strings.
Definition string_type.h:61
Functions related to OTTD's strings.
StrongType::Typedef< uint32_t, struct StringIndexInTabTag, StrongType::Compare, StrongType::Integer > StringIndexInTab
The index/offset of a string within a StringTab.
std::vector< LanguageStrings > raw_strings
The raw strings per language, first must be English/the master language!.
Definition game_text.hpp:57
TypedIndexContainer< StringList, StringIndexInTab > string_names
The names of the compiled strings.
Definition game_text.hpp:59
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
void Compile()
Compile the language.
TypedIndexContainer< StringParamsList, StringIndexInTab > string_params
The parameters for the strings.
Definition game_text.hpp:60
Base class for writing the header, i.e.
Definition strgen.h:90
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
Header of a language file.
Definition language.h:25
Container for the raw (unencoded) language strings of a language.
Definition game_text.hpp:40
TypedIndexContainer< StringList, StringIndexInTab > 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:111
virtual void WriteLang(const StringData &data)
Actually write the language.
std::array< const CmdStruct *, 32 > consuming_commands
Ordered by param #.
Definition strgen.h:148
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:83
StringList::const_iterator p
The current location of the iteration.
Definition game_text.cpp:84
StringList::const_iterator end
The end of the iteration.
Definition game_text.cpp:85
StringListReader(StringData &data, const LanguageStrings &strings, bool master, bool translation)
Create the reader.
Definition game_text.cpp:94
std::optional< std::string > ReadLine() override
Read a single line from the source of strings.
Definition game_text.cpp:99
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.
StringReader(StringData &data, const std::string &file, bool master, bool translation)
Prepare reading.
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
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.
Structs, typedefs and macros used for TAR file handling.
std::string FS2OTTD(std::wstring_view name)
Convert to OpenTTD's encoding from a wide string.
Definition win32.cpp:372