changeset 18786:5c8667c0c882 draft

(svn r23634) -Add: support language files for GameScript (Rubidium)
author truebrain <truebrain@openttd.org>
date Mon, 19 Dec 2011 21:05:46 +0000
parents 364318792aae
children 282d877efa74
files projects/openttd_vs100.vcxproj projects/openttd_vs100.vcxproj.filters projects/openttd_vs80.vcproj projects/openttd_vs90.vcproj source.list src/core/smallvec_type.hpp src/game/game.hpp src/game/game_core.cpp src/game/game_instance.cpp src/game/game_text.cpp src/game/game_text.hpp src/saveload/game_sl.cpp src/strings.cpp
diffstat 13 files changed, 593 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/projects/openttd_vs100.vcxproj
+++ b/projects/openttd_vs100.vcxproj
@@ -358,6 +358,7 @@
     <ClCompile Include="..\src\sprite.cpp" />
     <ClCompile Include="..\src\spritecache.cpp" />
     <ClCompile Include="..\src\station.cpp" />
+    <ClCompile Include="..\src\strgen\strgen_base.cpp" />
     <ClCompile Include="..\src\string.cpp" />
     <ClCompile Include="..\src\strings.cpp" />
     <ClCompile Include="..\src\subsidy.cpp" />
@@ -560,6 +561,7 @@
     <ClInclude Include="..\src\station_type.h" />
     <ClInclude Include="..\src\statusbar_gui.h" />
     <ClInclude Include="..\src\stdafx.h" />
+    <ClInclude Include="..\src\strgen\strgen.h" />
     <ClInclude Include="..\src\string_func.h" />
     <ClInclude Include="..\src\string_type.h" />
     <ClInclude Include="..\src\strings_func.h" />
@@ -931,6 +933,8 @@
     <ClInclude Include="..\src\game\game_instance.hpp" />
     <ClCompile Include="..\src\game\game_scanner.cpp" />
     <ClInclude Include="..\src\game\game_scanner.hpp" />
+    <ClCompile Include="..\src\game\game_text.cpp" />
+    <ClInclude Include="..\src\game\game_text.hpp" />
     <ClInclude Include="..\src\script\api\script_accounting.hpp" />
     <ClInclude Include="..\src\script\api\script_admin.hpp" />
     <ClInclude Include="..\src\script\api\script_airport.hpp" />
--- a/projects/openttd_vs100.vcxproj.filters
+++ b/projects/openttd_vs100.vcxproj.filters
@@ -303,6 +303,9 @@
     <ClCompile Include="..\src\station.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\strgen\strgen_base.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
     <ClCompile Include="..\src\string.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
@@ -909,6 +912,9 @@
     <ClInclude Include="..\src\stdafx.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="..\src\strgen\strgen.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
     <ClInclude Include="..\src\string_func.h">
       <Filter>Header Files</Filter>
     </ClInclude>
@@ -2022,6 +2028,12 @@
     <ClInclude Include="..\src\game\game_scanner.hpp">
       <Filter>Game Core</Filter>
     </ClInclude>
+    <ClCompile Include="..\src\game\game_text.cpp">
+      <Filter>Game Core</Filter>
+    </ClCompile>
+    <ClInclude Include="..\src\game\game_text.hpp">
+      <Filter>Game Core</Filter>
+    </ClInclude>
     <ClInclude Include="..\src\script\api\script_accounting.hpp">
       <Filter>Script API</Filter>
     </ClInclude>
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -703,6 +703,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\strgen\strgen_base.cpp"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\string.cpp"
 				>
 			</File>
@@ -1515,6 +1519,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\strgen\strgen.h"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\string_func.h"
 				>
 			</File>
@@ -3054,6 +3062,14 @@
 				RelativePath=".\..\src\game\game_scanner.hpp"
 				>
 			</File>
+			<File
+				RelativePath=".\..\src\game\game_text.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\..\src\game\game_text.hpp"
+				>
+			</File>
 		</Filter>
 		<Filter
 			Name="Script API"
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -700,6 +700,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\strgen\strgen_base.cpp"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\string.cpp"
 				>
 			</File>
@@ -1512,6 +1516,10 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\strgen\strgen.h"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\string_func.h"
 				>
 			</File>
@@ -3051,6 +3059,14 @@
 				RelativePath=".\..\src\game\game_scanner.hpp"
 				>
 			</File>
+			<File
+				RelativePath=".\..\src\game\game_text.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\..\src\game\game_text.hpp"
+				>
+			</File>
 		</Filter>
 		<Filter
 			Name="Script API"
--- a/source.list
+++ b/source.list
@@ -68,6 +68,7 @@
 sprite.cpp
 spritecache.cpp
 station.cpp
+strgen/strgen_base.cpp
 string.cpp
 strings.cpp
 subsidy.cpp
@@ -293,6 +294,7 @@
 station_type.h
 statusbar_gui.h
 stdafx.h
+strgen/strgen.h
 string_func.h
 string_type.h
 strings_func.h
@@ -707,6 +709,8 @@
 game/game_instance.hpp
 game/game_scanner.cpp
 game/game_scanner.hpp
+game/game_text.cpp
+game/game_text.hpp
 
 # Script API
 script/api/script_accounting.hpp
--- a/src/core/smallvec_type.hpp
+++ b/src/core/smallvec_type.hpp
@@ -326,6 +326,37 @@
 	}
 };
 
+/**
+ * Simple vector template class, with automatic delete.
+ *
+ * @note There are no asserts in the class so you have
+ *       to care about that you grab an item which is
+ *       inside the list.
+ *
+ * @param T The type of the items stored, must be a pointer
+ * @param S The steps of allocation
+ */
+template <typename T, uint S>
+class AutoDeleteSmallVector : public SmallVector<T, S> {
+public:
+	~AutoDeleteSmallVector()
+	{
+		this->Clear();
+	}
+
+	/**
+	 * Remove all items from the list.
+	 */
+	FORCEINLINE void Clear()
+	{
+		for (uint i = 0; i < this->items; i++) {
+			delete this->data[i];
+		}
+
+		this->items = 0;
+	}
+};
+
 typedef AutoFreeSmallVector<char*, 4> StringList; ///< Type for a list of strings.
 
 #endif /* SMALLVEC_TYPE_HPP */
--- a/src/game/game.hpp
+++ b/src/game/game.hpp
@@ -91,6 +91,11 @@
 	 */
 	static class GameInstance *GetInstance() { return Game::instance; }
 
+	/**
+	 * Get the current active mainscript.
+	 */
+	static const char *GetMainScript();
+
 #if defined(ENABLE_NETWORK)
 	/** Wrapper function for GameScanner::HasGame */
 	static bool HasGame(const struct ContentInfo *ci, bool md5sum);
--- a/src/game/game_core.cpp
+++ b/src/game/game_core.cpp
@@ -22,6 +22,7 @@
 #include "game_scanner.hpp"
 #include "game_config.hpp"
 #include "game_instance.hpp"
+#include "game_info.hpp"
 
 /* static */ uint Game::frame_counter = 0;
 /* static */ GameInfo *Game::info = NULL;
@@ -29,6 +30,11 @@
 /* static */ GameScannerInfo *Game::scanner_info = NULL;
 /* static */ GameScannerLibrary *Game::scanner_library = NULL;
 
+/* static */ const char *Game::GetMainScript()
+{
+		return Game::info->GetMainScript();
+}
+
 /* static */ void Game::GameLoop()
 {
 	if (_networking && !_network_server) return;
--- a/src/game/game_instance.cpp
+++ b/src/game/game_instance.cpp
@@ -19,6 +19,7 @@
 #include "game_config.hpp"
 #include "game_info.hpp"
 #include "game_instance.hpp"
+#include "game_text.hpp"
 #include "game.hpp"
 
 /* Convert all Game related classes to Squirrel data.
@@ -180,6 +181,7 @@
 	SQGSWaypointList_Vehicle_Register(this->engine);
 	SQGSWindow_Register(this->engine);
 
+	RegisterGameTranslation(this->engine);
 }
 
 int GameInstance::GetSetting(const char *name)
new file mode 100644
--- /dev/null
+++ b/src/game/game_text.cpp
@@ -0,0 +1,384 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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/>.
+ */
+
+/** @file game_text.cpp Implementation of handling translated strings. */
+
+#include "../stdafx.h"
+#include "../language.h"
+#include "../strgen/strgen.h"
+#include "../debug.h"
+#include "../fileio_func.h"
+#include "../script/squirrel_class.hpp"
+#include "../strings_func.h"
+#include "game_text.hpp"
+#include "game.hpp"
+
+#include "table/strings.h"
+
+#include <exception>
+#include <stdarg.h>
+
+void CDECL strgen_warning(const char *s, ...)
+{
+	char buf[1024];
+	va_list va;
+	va_start(va, s);
+	vsnprintf(buf, lengthof(buf), s, va);
+	va_end(va);
+	DEBUG(script, 0, "%s:%d: warning: %s", _file, _cur_line, buf);
+	_warnings++;
+}
+
+void CDECL strgen_error(const char *s, ...)
+{
+	char buf[1024];
+	va_list va;
+	va_start(va, s);
+	vsnprintf(buf, lengthof(buf), s, va);
+	va_end(va);
+	DEBUG(script, 0, "%s:%d: error: %s", _file, _cur_line, buf);
+	_errors++;
+}
+
+void NORETURN CDECL strgen_fatal(const char *s, ...)
+{
+	char buf[1024];
+	va_list va;
+	va_start(va, s);
+	vsnprintf(buf, lengthof(buf), s, va);
+	va_end(va);
+	DEBUG(script, 0, "%s:%d: FATAL: %s", _file, _cur_line, buf);
+	throw std::exception();
+}
+
+/**
+ * Create a new container for language strings.
+ * @param language The language name.
+ */
+LanguageStrings::LanguageStrings(const char *language)
+{
+	const char *p = strrchr(language, PATHSEPCHAR);
+	if (p == NULL) {
+		p = language;
+	} else {
+		p++;
+	}
+
+	const char *e = strchr(p, '.');
+	this->language = e == NULL ? strdup(p) : strndup(p, e - p);
+}
+
+/** Free everything. */
+LanguageStrings::~LanguageStrings()
+{
+	free(this->language);
+}
+
+/**
+ * Read all the raw language strings from the given file.
+ * @param file The file to read from.
+ * @return The raw strings, or NULL upon error.
+ */
+LanguageStrings *ReadRawLanguageStrings(const char *file)
+{
+	LanguageStrings *ret = NULL;
+	try {
+		size_t to_read;
+		FILE *fh = FioFOpenFile(file, "rb", GAME_DIR, &to_read);
+		if (fh == NULL) {
+			return NULL;
+		}
+
+		ret = new LanguageStrings(file);
+
+		char buffer[2048];
+		while (to_read != 0 && fgets(buffer, sizeof(buffer), fh) != NULL) {
+			size_t len = strlen(buffer);
+
+			/* Remove trailing spaces/newlines from the string. */
+			size_t i = len;
+			while (i > 0 && (buffer[i - 1] == '\r' || buffer[i - 1] == '\n' || buffer[i - 1] == ' ')) i--;
+			buffer[i] = '\0';
+
+			*ret->lines.Append() = strndup(buffer, to_read);
+
+			if (len > to_read) {
+				to_read = 0;
+			} else {
+				to_read -= len;
+			}
+		}
+
+		return ret;
+	} catch (...) {
+		delete ret;
+		return NULL;
+	}
+}
+
+
+/** A reader that simply reads using fopen. */
+struct StringListReader : StringReader {
+	const char * const *p;   ///< The current location of the iteration.
+	const char * const *end; ///< The end of the iteration.
+
+	/**
+	 * Create the reader.
+	 * @param data        The data to fill during reading.
+	 * @param file        The file we are reading.
+	 * @param master      Are we reading the master file?
+	 * @param translation Are we reading a translation?
+	 */
+	StringListReader(StringData &data, const LanguageStrings *strings, bool master, bool translation) :
+			StringReader(data, strings->language, master, translation), p(strings->lines.Begin()), end(strings->lines.End())
+	{
+	}
+
+	/* virtual */ char *ReadLine(char *buffer, size_t size)
+	{
+		if (this->p == this->end) return NULL;
+
+		strncpy(buffer, *this->p, size);
+		this->p++;
+
+		return buffer;
+	}
+
+	/* virtual */ void HandlePragma(char *str)
+	{
+		strgen_fatal("unknown pragma '%s'", str);
+	}
+};
+
+/** Class for writing an encoded language. */
+struct TranslationWriter : LanguageWriter {
+	StringList *strings; ///< The encoded strings.
+
+	/**
+	 * Writer for the encoded data.
+	 * @param strings The string table to add the strings to.
+	 */
+	TranslationWriter(StringList *strings) : strings(strings)
+	{
+	}
+
+	void WriteHeader(const LanguagePackHeader *header)
+	{
+		/* We don't use the header. */
+	}
+
+	void Finalise()
+	{
+		/* Nothing to do. */
+	}
+
+	void WriteLength(uint length)
+	{
+		/* We don't write the length. */
+	}
+
+	void Write(const byte *buffer, size_t length)
+	{
+		*this->strings->Append() = strndup((const char*)buffer, length);
+	}
+};
+
+/** Class for writing the string IDs. */
+struct StringNameWriter : HeaderWriter {
+	StringList *strings; ///< The string names.
+
+	/**
+	 * Writer for the string names.
+	 * @param strings The string table to add the strings to.
+	 */
+	StringNameWriter(StringList *strings) : strings(strings)
+	{
+	}
+
+	void WriteStringID(const char *name, int stringid)
+	{
+		if (stringid == (int)this->strings->Length()) *this->strings->Append() = strdup(name);
+	}
+
+	void Finalise(const StringData &data)
+	{
+		/* Nothing to do. */
+	}
+};
+
+static void GetBasePath(char *buffer, size_t length)
+{
+	strecpy(buffer, Game::GetMainScript(), buffer + length);
+	char *s = strrchr(buffer, PATHSEPCHAR);
+	if (s != NULL) {
+		/* Keep the PATHSEPCHAR there, remove the rest */
+		s++;
+		*s = '\0';
+	}
+
+	/* Tars dislike opening files with '/' on Windows.. so convert it to '\\' */
+#if (PATHSEPCHAR != '/')
+	for (char *n = buffer; *n != '\0'; n++) if (*n == '/') *n = PATHSEPCHAR;
+#endif
+}
+
+/**
+ * Scanner to find language files in a GameScript directory.
+ */
+class LanguageScanner : protected FileScanner {
+private:
+	GameStrings *gs;
+	char *exclude;
+
+public:
+	/** Initialise */
+	LanguageScanner(GameStrings *gs, const char *exclude) : gs(gs), exclude(strdup(exclude)) {}
+	~LanguageScanner() { free(exclude); }
+
+	/**
+	 * Scan.
+	 */
+	void Scan(const char *directory)
+	{
+		this->FileScanner::Scan(".txt", directory, false);
+	}
+
+	/* virtual */ bool AddFile(const char *filename, size_t basepath_length, const char *tar_filename)
+	{
+		if (strcmp(filename, exclude) == 0) return true;
+
+		*gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
+		return true;
+	}
+};
+
+/**
+ * Load all translations that we know of.
+ * @return Container with all (compiled) translations.
+ */
+GameStrings *LoadTranslations()
+{
+	GameStrings *gs = new GameStrings();
+	try {
+		char filename[512];
+		GetBasePath(filename, sizeof(filename));
+		char *e = filename + strlen(filename);
+
+		seprintf(e, filename + sizeof(filename), "lang" PATHSEP "english.txt");
+		if (!FioCheckFileExists(filename, GAME_DIR)) throw std::exception();
+		*gs->raw_strings.Append() = ReadRawLanguageStrings(filename);
+
+		/* Scan for other language files */
+		LanguageScanner scanner(gs, filename);
+		strecpy(e, "lang" PATHSEP, filename + sizeof(filename));
+		scanner.Scan(filename);
+
+		gs->Compile();
+		return gs;
+	} catch (...) {
+		delete gs;
+		return NULL;
+	}
+}
+
+/** Compile the language. */
+void GameStrings::Compile()
+{
+	StringData data(1);
+	StringListReader master_reader(data, this->raw_strings[0], true, false);
+	master_reader.ParseFile();
+	if (_errors != 0) throw std::exception();
+
+	this->version = data.Version();
+
+	StringNameWriter id_writer(&this->string_names);
+	id_writer.WriteHeader(data);
+
+	for (LanguageStrings **p = this->raw_strings.Begin(); p != this->raw_strings.End(); p++) {
+		data.FreeTranslation();
+		StringListReader translation_reader(data, *p, false, strcmp((*p)->language, "english") != 0);
+		translation_reader.ParseFile();
+		if (_errors != 0) throw std::exception();
+
+		LanguageStrings *compiled = *this->compiled_strings.Append() = new LanguageStrings((*p)->language);
+		TranslationWriter writer(&compiled->lines);
+		writer.WriteLang(data);
+	}
+}
+
+/** The currently loaded game strings. */
+GameStrings *_current_data = NULL;
+
+/**
+ * Get the string pointer of a particular game string.
+ * @param id The ID of the game string.
+ * @return The encoded string.
+ */
+const char *GetGameStringPtr(uint id)
+{
+	if (id >= _current_data->cur_language->lines.Length()) return GetStringPtr(STR_UNDEFINED);
+	return _current_data->cur_language->lines[id];
+}
+
+/**
+ * Register the current translation to the Squirrel engine.
+ * @param engine The engine to update/
+ */
+void RegisterGameTranslation(Squirrel *engine)
+{
+	delete _current_data;
+	_current_data = LoadTranslations();
+	if (_current_data == NULL) return;
+
+	HSQUIRRELVM vm = engine->GetVM();
+	sq_pushroottable(vm);
+	sq_pushstring(vm, _SC("GSText"), -1);
+	if (SQ_FAILED(sq_get(vm, -2))) return;
+
+	int idx = 0;
+	for (const char * const *p = _current_data->string_names.Begin(); p != _current_data->string_names.End(); p++, idx++) {
+		sq_pushstring(vm, OTTD2SQ(*p), -1);
+		sq_pushinteger(vm, idx);
+		sq_rawset(vm, -3);
+	}
+
+	sq_pop(vm, 2);
+
+	ReconsiderGameScriptLanguage();
+}
+
+/**
+ * Reconsider the game script language, so we use the right one.
+ */
+void ReconsiderGameScriptLanguage()
+{
+	if (_current_data == NULL) return;
+
+	char temp[MAX_PATH];
+	strecpy(temp, _current_language->file, temp + sizeof(temp));
+
+	/* Remove the extension */
+	char *l = strrchr(temp, '.');
+	assert(l != NULL);
+	*l = '\0';
+
+	/* Skip the path */
+	char *language = strrchr(temp, PATHSEPCHAR);
+	assert(language != NULL);
+	language++;
+
+	for (LanguageStrings **p = _current_data->compiled_strings.Begin(); p != _current_data->compiled_strings.End(); p++) {
+		if (strcmp((*p)->language, language) == 0) {
+			_current_data->cur_language = *p;
+			return;
+		}
+	}
+
+	_current_data->cur_language = _current_data->compiled_strings[0];
+}
new file mode 100644
--- /dev/null
+++ b/src/game/game_text.hpp
@@ -0,0 +1,45 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * 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.
+ * 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.
+ * 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/>.
+ */
+
+/** @file game_text.hpp Base functions regarding game texts. */
+
+#ifndef GAME_TEXT_HPP
+#define GAME_TEXT_HPP
+
+#include "../core/smallvec_type.hpp"
+
+/** The tab we place our strings in. */
+static const uint GAME_TEXT_TAB = 18;
+
+const char *GetGameStringPtr(uint id);
+void RegisterGameTranslation(class Squirrel *engine);
+void ReconsiderGameScriptLanguage();
+
+/** Container for the raw (unencoded) language strings of a language. */
+struct LanguageStrings {
+	const char *language; ///< Name of the language (base filename).
+	StringList lines;     ///< The lines of the file to pass into the parser/encoder.
+
+	LanguageStrings(const char *language);
+	~LanguageStrings();
+};
+
+/** Container for all the game strings. */
+struct GameStrings {
+	uint version;                  ///< The version of the language strings.
+	LanguageStrings *cur_language; ///< The current (compiled) language.
+
+	AutoDeleteSmallVector<LanguageStrings *, 4> raw_strings;      ///< The raw strings per language, first must be English/the master language!.
+	AutoDeleteSmallVector<LanguageStrings *, 4> compiled_strings; ///< The compiled strings per language, first must be English/the master language!.
+	StringList string_names;                                      ///< The names of the compiled strings.
+
+	void Compile();
+};
+
+#endif /* GAME_TEXT_HPP */
--- a/src/saveload/game_sl.cpp
+++ b/src/saveload/game_sl.cpp
@@ -19,6 +19,7 @@
 #include "../game/game_config.hpp"
 #include "../network/network.h"
 #include "../game/game_instance.hpp"
+#include "../game/game_text.hpp"
 
 static char _game_saveload_name[64];
 static int  _game_saveload_version;
@@ -111,6 +112,67 @@
 	SlAutolength((AutolengthProc *)SaveReal_GSDT, NULL);
 }
 
+extern GameStrings *_current_data;
+
+static const char *_game_saveload_string;
+static uint _game_saveload_strings;
+
+static const SaveLoad _game_language_header[] = {
+	SLEG_STR(_game_saveload_string, SLE_STR),
+	SLEG_VAR(_game_saveload_strings, SLE_UINT32),
+	 SLE_END()
+};
+
+static const SaveLoad _game_language_string[] = {
+	SLEG_STR(_game_saveload_string, SLE_STR | SLF_ALLOW_CONTROL),
+	 SLE_END()
+};
+
+static void SaveReal_GSTR(LanguageStrings *ls)
+{
+	_game_saveload_string  = ls->language;
+	_game_saveload_strings = ls->lines.Length();
+
+	SlObject(NULL, _game_language_header);
+	for (uint i = 0; i < _game_saveload_strings; i++) {
+		_game_saveload_string = ls->lines[i];
+		SlObject(NULL, _game_language_string);
+	}
+}
+
+static void Load_GSTR()
+{
+	delete _current_data;
+	_current_data = new GameStrings();
+
+	while (SlIterateArray() != -1) {
+		_game_saveload_string = NULL;
+		SlObject(NULL, _game_language_header);
+
+		LanguageStrings *ls = new LanguageStrings(_game_saveload_string);
+		for (uint i = 0; i < _game_saveload_strings; i++) {
+			SlObject(NULL, _game_language_string);
+			*ls->lines.Append() = strdup(_game_saveload_string);
+		}
+
+		*_current_data->raw_strings.Append() = ls;
+	}
+
+	_current_data->Compile();
+	ReconsiderGameScriptLanguage();
+}
+
+static void Save_GSTR()
+{
+	if (_current_data == NULL) return;
+
+	for (uint i = 0; i < _current_data->raw_strings.Length(); i++) {
+		SlSetArrayIndex(i);
+		SlAutolength((AutolengthProc *)SaveReal_GSTR, _current_data->raw_strings[i]);
+	}
+}
+
 extern const ChunkHandler _game_chunk_handlers[] = {
+	{ 'GSTR', Save_GSTR, Load_GSTR, NULL, NULL, CH_ARRAY },
 	{ 'GSDT', Save_GSDT, Load_GSDT, NULL, NULL, CH_ARRAY | CH_LAST},
 };
--- a/src/strings.cpp
+++ b/src/strings.cpp
@@ -38,6 +38,7 @@
 #include "smallmap_gui.h"
 #include "window_func.h"
 #include "debug.h"
+#include "game/game_text.hpp"
 #include <stack>
 
 #include "table/strings.h"
@@ -140,6 +141,7 @@
 const char *GetStringPtr(StringID string)
 {
 	switch (GB(string, TAB_COUNT_OFFSET, TAB_COUNT_BITS)) {
+		case GAME_TEXT_TAB: return GetGameStringPtr(GB(string, TAB_SIZE_OFFSET, TAB_SIZE_BITS));
 		/* GetGRFStringPtr doesn't handle 0xD4xx ids, we need to convert those to 0xD0xx. */
 		case 26: return GetStringPtr(GetGRFStringID(0, 0xD000 + GB(string, TAB_SIZE_OFFSET, 10)));
 		case 28: return GetGRFStringPtr(GB(string, TAB_SIZE_OFFSET, TAB_SIZE_BITS));
@@ -182,6 +184,9 @@
 			/* Old table for custom names. This is no longer used */
 			error("Incorrect conversion of custom name string.");
 
+		case GAME_TEXT_TAB:
+			return FormatString(buffr, GetGameStringPtr(index), args, last, case_index);
+
 		case 26:
 			/* Include string within newgrf text (format code 81) */
 			if (HasBit(index, 10)) {
@@ -1611,6 +1616,7 @@
 #endif /* WITH_ICU */
 
 	/* Some lists need to be sorted again after a language change. */
+	ReconsiderGameScriptLanguage();
 	InitializeSortedCargoSpecs();
 	SortIndustryTypes();
 	BuildIndustriesLegend();