OpenTTD

- Main Page
- Related Pages
- Modules
- Namespaces
- Data Structures
- Files

- File List
- Globals

game_text.cpp
Go to the documentation of this file.
1 /* $Id: game_text.cpp 26774 2014-09-06 17:46:56Z rubidium $ */
2
3 /*
4 * This file is part of OpenTTD.
5 * 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.
6 * 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.
7 * 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/>.
8 */
9
12 #include "../stdafx.h"
13 #include "../strgen/strgen.h"
14 #include "../debug.h"
15 #include "../fileio_func.h"
16 #include "../tar_type.h"
17 #include "../script/squirrel_class.hpp"
18 #include "../strings_func.h"
19 #include "game_text.hpp"
20 #include "game.hpp"
21 #include "game_info.hpp"
22
23 #include "table/strings.h"
24
25 #include <stdarg.h>
26
27 #include "../safeguards.h"
28
29 void CDECL strgen_warning(const char *s, ...)
30 {
31 char buf[1024];
32 va_list va;
33 va_start(va, s);
34 vseprintf(buf, lastof(buf), s, va);
35 va_end(va);
36 DEBUG(script, 0, "%s:%d: warning: %s", _file, _cur_line, buf);
37 _warnings++;
38 }
39
40 void CDECL strgen_error(const char *s, ...)
41 {
42 char buf[1024];
43 va_list va;
44 va_start(va, s);
45 vseprintf(buf, lastof(buf), s, va);
46 va_end(va);
47 DEBUG(script, 0, "%s:%d: error: %s", _file, _cur_line, buf);
48 _errors++;
49 }
50
51 void NORETURN CDECL strgen_fatal(const char *s, ...)
52 {
53 char buf[1024];
54 va_list va;
55 va_start(va, s);
56 vseprintf(buf, lastof(buf), s, va);
57 va_end(va);
58 DEBUG(script, 0, "%s:%d: FATAL: %s", _file, _cur_line, buf);
59 throw std::exception();
60 }
61
67 LanguageStrings::LanguageStrings(const char *language, const char *end)
68 {
69 this->language = stredup(language, end != NULL ? end - 1 : NULL);
70 }
71
73 LanguageStrings::~LanguageStrings()
74 {
75 free(this->language);
76 }
77
83 LanguageStrings *ReadRawLanguageStrings(const char *file)
84 {
85 LanguageStrings *ret = NULL;
86 FILE *fh = NULL;
87 try {
88 size_t to_read;
89 fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
90 if (fh == NULL) {
91 return NULL;
92 }
93
94 const char *langname = strrchr(file, PATHSEPCHAR);
95 if (langname == NULL) {
96 langname = file;
97 } else {
98 langname++;
99 }
100
101 /* Check for invalid empty filename */
102 if (*langname == '.' || *langname == 0) {
103 fclose(fh);
104 return NULL;
105 }
106
107 ret = new LanguageStrings(langname, strchr(langname, '.'));
108
109 char buffer[2048];
110 while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != NULL) {
111 size_t len = strlen(buffer);
112
113 /* Remove trailing spaces/newlines from the string. */
114 size_t i = len;
115 while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
116 buffer[i] = '\0';
117
118 *ret->lines.Append() = stredup(buffer, buffer + to_read - 1);
119
120 if (len > to_read) {
121 to_read = 0;
122 } else {
123 to_read -= len;
124 }
125 }
126
127 fclose(fh);
128 return ret;
129 } catch (...) {
130 if (fh != NULL) fclose(fh);
131 delete ret;
132 return NULL;
133 }
134 }
135
136
138 struct StringListReader : StringReader {
139 const char * const *p;
140 const char * const *end;
141
149 StringListReader(StringData &data, const LanguageStrings *strings, bool master, bool translation) :
150 StringReader(data, strings->language, master, translation), p(strings->lines.Begin()), end(strings->lines.End())
151 {
152 }
153
154 /* virtual */ char *ReadLine(char *buffer, const char *last)
155 {
156 if (this->p == this->end) return NULL;
157
158 strecpy(buffer, *this->p, last);
159 this->p++;
160
161 return buffer;
162 }
163 };
164
166 struct TranslationWriter : LanguageWriter {
167 StringList *strings;
168
173 TranslationWriter(StringList *strings) : strings(strings)
174 {
175 }
176
177 void WriteHeader(const LanguagePackHeader *header)
178 {
179 /* We don't use the header. */
180 }
181
182 void Finalise()
183 {
184 /* Nothing to do. */
185 }
186
187 void WriteLength(uint length)
188 {
189 /* We don't write the length. */
190 }
191
192 void Write(const byte *buffer, size_t length)
193 {
194 char *dest = MallocT<char>(length + 1);
195 memcpy(dest, buffer, length);
196 dest[length] = '\0';
197 *this->strings->Append() = dest;
198 }
199 };
200
202 struct StringNameWriter : HeaderWriter {
203 StringList *strings;
204
209 StringNameWriter(StringList *strings) : strings(strings)
210 {
211 }
212
213 void WriteStringID(const char *name, int stringid)
214 {
215 if (stringid == (int)this->strings->Length()) *this->strings->Append() = stredup(name);
216 }
217
218 void Finalise(const StringData &data)
219 {
220 /* Nothing to do. */
221 }
222 };
223
227 class LanguageScanner : protected FileScanner {
228 private:
229 GameStrings *gs;
230 char *exclude;
231
232 public:
234 LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(stredup(exclude)) {}
235 ~LanguageScanner() { free(exclude); }
236
240 void Scan(const char *directory)
241 {
242 this->FileScanner::Scan(".txt", directory, false);
243 }
244
245 /* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
246 {
247 if (strcmp(filename, exclude) == 0) return true;
248
249 *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
250 return true;
251 }
252 };
253
258 GameStrings *LoadTranslations()
259 {
260 const GameInfo *info = Game::GetInfo();
261 char filename[512];
262 strecpy(filename, info->GetMainScript(), lastof(filename));
263 char *e = strrchr(filename, PATHSEPCHAR);
264 if (e == NULL) return NULL;
265 e++; // Make 'e' point after the PATHSEPCHAR
266
267 strecpy(e, "lang" PATHSEP "english.txt", lastof(filename));
268 if (!FioCheckFileExists(filename, GAME_DIR)) return NULL;
269
270 GameStrings *gs = new GameStrings();
271 try {
272 *gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
273
274 /* Scan for other language files */
275 LanguageScanner scanner(gs, filename);
276 strecpy(e, "lang" PATHSEP, lastof(filename));
277 size_t len = strlen(filename);
278
279 const char *tar_filename = info->GetTarFile();
280 TarList::iterator iter;
281 if (tar_filename != NULL && (iter = _tar_list[GAME_DIR].find(tar_filename)) != _tar_list[GAME_DIR].end()) {
282 /* The main script is in a tar file, so find all files that
283 * are in the same tar and add them to the langfile scanner. */
284 TarFileList::iterator tar;
285 FOR_ALL_TARS(tar, GAME_DIR) {
286 /* Not in the same tar. */
287 if (tar->second.tar_filename != iter->first) continue;
288
289 /* Check the path and extension. */
290 if (tar->first.size() <= len || tar->first.compare(0, len, filename) != 0) continue;
291 if (tar->first.compare(tar->first.size() - 4, 4, ".txt") != 0) continue;
292
293 scanner.AddFile(tar->first.c_str(), 0, tar_filename);
294 }
295 } else {
296 /* Scan filesystem */
297 scanner.Scan(filename);
298 }
299
300 gs->Compile();
301 return gs;
302 } catch (...) {
303 delete gs;
304 return NULL;
305 }
306 }
307
309 void GameStrings::Compile()
310 {
311 StringData data(1);
312 StringListReader master_reader(data, this->raw_strings[0], true, false);
313 master_reader.ParseFile();
314 if (_errors != 0) throw std::exception();
315
316 this->version = data.Version();
317
318 StringNameWriter id_writer(&this->string_names);
319 id_writer.WriteHeader(data);
320
321 for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
322 data.FreeTranslation();
323 StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
324 translation_reader.ParseFile();
325 if (_errors != 0) throw std::exception();
326
327 LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
328 TranslationWriter writer(&compiled->lines);
329 writer.WriteLang(data);
330 }
331 }
332
334 GameStrings *_current_data = NULL;
335
341 const char *GetGameStringPtr(uint id)
342 {
343 if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
344 return _current_data->cur_language->lines[id];
345 }
346
351 void RegisterGameTranslation(Squirrel *engine)
352 {
353 delete _current_data;
354 _current_data = LoadTranslations();
355 if (_current_data == NULL) return;
356
357 HSQUIRRELVM vm = engine->GetVM();
358 sq_pushroottable(vm);
359 sq_pushstring(vm, "GSText", -1);
360 if (SQ_FAILED(sq_get(vm, -2))) return;
361
362 int idx = 0;
363 for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
364 sq_pushstring(vm, *p, -1);
365 sq_pushinteger(vm, idx);
366 sq_rawset(vm, -3);
367 }
368
369 sq_pop(vm, 2);
370
371 ReconsiderGameScriptLanguage();
372 }
373
377 void ReconsiderGameScriptLanguage()
378 {
379 if (_current_data == NULL) return;
380
381 char temp[MAX_PATH];
382 strecpy(temp, _current_language->file, lastof(temp));
383
384 /* Remove the extension */
385 char *l = strrchr(temp, '.');
386 assert(l != NULL);
387 *l = '\0';
388
389 /* Skip the path */
390 char *language = strrchr(temp, PATHSEPCHAR);
391 assert(language != NULL);
392 language++;
393
394 for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
395 if (strcmp((*p)->language, language) == 0) {
396 _current_data->cur_language = *p;
397 return;
398 }
399 }
400
401 _current_data->cur_language = _current_data->compiled_strings[0];
402 }