diff --git a/projects/openrct2.vcxproj b/projects/openrct2.vcxproj
index 67022bd317..688622c8a5 100644
--- a/projects/openrct2.vcxproj
+++ b/projects/openrct2.vcxproj
@@ -65,6 +65,7 @@
+
@@ -235,6 +236,7 @@
+
diff --git a/projects/openrct2.vcxproj.filters b/projects/openrct2.vcxproj.filters
index b82c90de10..9cd193edaf 100644
--- a/projects/openrct2.vcxproj.filters
+++ b/projects/openrct2.vcxproj.filters
@@ -537,6 +537,9 @@
Source\World
+
+ Source\Localisation
+
@@ -788,5 +791,8 @@
Source\Core
+
+ Source\Localisation
+
\ No newline at end of file
diff --git a/src/localisation/LanguagePack.cpp b/src/localisation/LanguagePack.cpp
new file mode 100644
index 0000000000..a9deaa3781
--- /dev/null
+++ b/src/localisation/LanguagePack.cpp
@@ -0,0 +1,200 @@
+extern "C" {
+ #include "../common.h"
+ #include "../util/util.h"
+ #include "localisation.h"
+}
+
+#include "LanguagePack.h"
+
+#include
+
+// TODO Move to separate file
+class StringBuilder final {
+public:
+ StringBuilder()
+ {
+ _buffer = NULL;
+ _capacity = 0;
+ _length = 0;
+ }
+
+ StringBuilder(int capacity) : StringBuilder()
+ {
+ EnsureCapacity(capacity);
+ }
+
+ ~StringBuilder()
+ {
+ if (_buffer != NULL) free(_buffer);
+ }
+
+ utf8 *GetString() const
+ {
+ utf8 *result = (utf8*)malloc(_length + 1);
+ memcpy(result, _buffer, _length);
+ result[_length] = 0;
+ return result;
+ }
+
+ void Append(int codepoint)
+ {
+ int codepointLength = utf8_get_codepoint_length(codepoint);
+ EnsureCapacity(_length + codepointLength + 1);
+ utf8_write_codepoint(_buffer + _length, codepoint);
+ _length += codepointLength;
+ }
+
+ void Append(utf8 *text)
+ {
+ int textLength = strlen(text);
+
+ EnsureCapacity(_length + textLength + 1);
+ memcpy(_buffer + _length, text, textLength);
+ _length += textLength;
+ }
+
+private:
+ utf8 *_buffer;
+ size_t _capacity;
+ size_t _length;
+
+ void EnsureCapacity(size_t capacity)
+ {
+ if (_capacity > capacity) return;
+
+ _capacity = max(8, _capacity);
+ while (_capacity < capacity) {
+ _capacity *= 2;
+ }
+
+ if (_buffer == NULL) {
+ _buffer = (utf8*)malloc(_capacity);
+ } else {
+ _buffer = (utf8*)realloc(_buffer, _capacity);
+ }
+ }
+};
+
+LanguagePack *LanguagePack::FromFile(int id, const utf8 *path)
+{
+ assert(path != NULL);
+
+ int fileLength;
+ utf8 *fileData;
+
+ // Load file directly into memory
+ SDL_RWops *file = SDL_RWFromFile(path, "rb");
+ if (file == NULL) {
+ log_error("Unable to open %s", path);
+ return NULL;
+ }
+
+ fileLength = (int)SDL_RWsize(file);
+ fileData = (utf8*)malloc(fileLength);
+ SDL_RWread(file, fileData, fileLength, 1);
+ SDL_RWclose(file);
+
+ // Parse the memory as text
+ LanguagePack *result = FromText(id, fileData);
+ free(fileData);
+
+ return result;
+}
+
+LanguagePack *LanguagePack::FromText(int id, const utf8 *text)
+{
+ return new LanguagePack(id, text);
+}
+
+LanguagePack::LanguagePack(int id, const utf8 *text)
+{
+ assert(text != NULL);
+
+ _id = id;
+ _stringData = NULL;
+ _currentGroup = NULL;
+
+ auto reader = UTF8StringReader(text);
+
+}
+
+LanguagePack::~LanguagePack()
+{
+ SafeFree(_stringData);
+ SafeFree(_currentGroup);
+}
+
+static void SkipWhitespace(IStringReader *reader)
+{
+ int codepoint;
+ while (reader->TryPeek(&codepoint)) {
+ if (codepoint == '\t' || codepoint == ' ' || codepoint == '\r' || codepoint == '\n') {
+ reader->Skip();
+ } else {
+ break;
+ }
+ }
+}
+
+static void SkipToEndOfLine(IStringReader *reader)
+{
+ int codepoint;
+ while (reader->TryPeek(&codepoint)) {
+ if (codepoint != '\r' && codepoint != '\n') {
+ reader->Skip();
+ } else {
+ break;
+ }
+ }
+}
+
+void LanguagePack::ParseLine(IStringReader *reader)
+{
+ SkipWhitespace(reader);
+
+ int codepoint;
+ if (!reader->TryPeek(&codepoint))
+ return;
+
+ switch (codepoint) {
+ case '#':
+ SkipToEndOfLine(reader);
+ break;
+ case '[':
+ ParseGroup(reader);
+ break;
+ default:
+ ParseString(reader);
+ break;
+ }
+}
+
+void LanguagePack::ParseGroup(IStringReader *reader)
+{
+ auto sb = StringBuilder();
+ int codepoint;
+
+ // Should have already deduced that the next codepoint is a [
+ reader->Skip();
+
+ // Read string up to ] or line end
+ bool closedCorrectly = false;
+ while (reader->TryRead(&codepoint)) {
+ if (codepoint == '\n' || codepoint == '\r') break;
+ if (codepoint == ']') {
+ closedCorrectly = true;
+ break;
+ }
+ sb.Append(codepoint);
+ }
+
+ if (closedCorrectly) {
+ SafeFree(_currentGroup);
+ _currentGroup = sb.GetString();
+ }
+}
+
+void LanguagePack::ParseString(IStringReader *reader)
+{
+
+}
diff --git a/src/localisation/LanguagePack.h b/src/localisation/LanguagePack.h
new file mode 100644
index 0000000000..a66bc02093
--- /dev/null
+++ b/src/localisation/LanguagePack.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#include
+
+extern "C" {
+ #include "../common.h"
+ #include "../util/util.h"
+ #include "localisation.h"
+}
+
+struct IStringReader abstract {
+ virtual bool TryPeek(int *outCodepoint) abstract;
+ virtual bool TryRead(int *outCodepoint) abstract;
+ virtual void Skip() abstract;
+};
+
+// TODO Move to separate file in Core
+class UTF8StringReader final : public IStringReader {
+public:
+ UTF8StringReader(const utf8 *text)
+ {
+ // Skip UTF-8 byte order mark
+ if (strlen(text) >= 3 && utf8_is_bom(text)) {
+ text += 3;
+ }
+
+ _text = text;
+ _current = text;
+ }
+
+ bool TryPeek(int *outCodepoint) override
+ {
+ if (_current == NULL) return false;
+
+ int codepoint = utf8_get_next(_current, NULL);
+ *outCodepoint = codepoint;
+ return true;
+ }
+
+ bool TryRead(int *outCodepoint) override
+ {
+ if (_current == NULL) return false;
+
+ int codepoint = utf8_get_next(_current, &_current);
+ *outCodepoint = codepoint;
+ if (codepoint == 0) {
+ _current = NULL;
+ return false;
+ }
+ return true;
+ }
+
+ void Skip() override
+ {
+ int codepoint;
+ TryRead(&codepoint);
+ }
+
+private:
+ const utf8 *_text;
+ const utf8 *_current;
+};
+
+class LanguagePack final {
+public:
+ static LanguagePack *FromFile(int id, const utf8 *path);
+ static LanguagePack *FromText(int id, const utf8 *text);
+
+ ~LanguagePack();
+
+ int GetId() const { return _id; }
+ int GetCount() const { return _strings.size(); }
+
+private:
+ struct ObjectOverride {
+ char name[8];
+ const utf8 *strings[4];
+ };
+
+ int _id;
+ utf8 *_stringData;
+ std::vector _strings;
+
+ LanguagePack(int id, const utf8 *text);
+
+ ///////////////////////////////////////////////////////////////////////////
+ // Parsing
+ ///////////////////////////////////////////////////////////////////////////
+ utf8 *_currentGroup;
+
+ void ParseLine(IStringReader *reader);
+ void ParseGroup(IStringReader *reader);
+ void ParseString(IStringReader *reader);
+};
diff --git a/src/localisation/language.c b/src/localisation/language.c
index 089f4f25b9..d2bc8557fe 100644
--- a/src/localisation/language.c
+++ b/src/localisation/language.c
@@ -228,6 +228,8 @@ static int language_open_file(const utf8 *filename, language_data *language)
char *dst = NULL;
char *token = NULL;
char tokenBuffer[64];
+ char groupBuffer[64];
+ int groupLength = 0;
int stringIndex = 0, mode = 0, stringId, maxStringId = 0;
size_t i = 0;
@@ -248,18 +250,39 @@ static int language_open_file(const utf8 *filename, language_data *language)
// Search for a comment
if (utf8Char == '#') {
mode = 3;
- } else if (utf8Char == ':' && stringId != -1) {
- // Search for colon
- dst = src + 1;
- language->strings[stringId] = dst;
- stringIndex++;
- mode = 1;
- } else if (!strncmp(src, "STR_", 4)){
- // Copy in the string number, 4 characters only
- if (sscanf(src, "STR_%4d", &stringId) != 1) {
- stringId = -1;
- } else {
- maxStringId = max(maxStringId, stringId);
+ } else if (utf8Char == '[') {
+ mode = 4;
+ }
+
+ if (groupLength == 0) {
+ if (utf8Char == ':' && stringId != -1) {
+ // Search for colon
+ dst = src + 1;
+ language->strings[stringId] = dst;
+ stringIndex++;
+ mode = 1;
+ } else if (!strncmp(src, "STR_", 4)) {
+ // Copy in the string number, 4 characters only
+ if (sscanf(src, "STR_%4d", &stringId) != 1) {
+ stringId = -1;
+ } else {
+ maxStringId = max(maxStringId, stringId);
+ }
+ }
+ } else {
+ if (utf8Char == ':' && stringId != -1) {
+ // Search for colon
+ dst = src + 1;
+ language->strings[stringId] = dst;
+ stringIndex++;
+ mode = 1;
+ } else if (!strncmp(src, "STR_", 4)) {
+ // Copy in the string number, 4 characters only
+ if (sscanf(src, "STR_%4d", &stringId) != 1) {
+ stringId = -1;
+ } else {
+ maxStringId = max(maxStringId, stringId);
+ }
}
}
break;
@@ -295,6 +318,21 @@ static int language_open_file(const utf8 *filename, language_data *language)
if (utf8Char == '\n' || utf8Char == '\r') {
mode = 0;
}
+ break;
+ case 4:
+ if (utf8Char == '\n' || utf8Char == '\r') {
+ groupLength = 0;
+ mode = 0;
+ } else if (utf8Char == ']') {
+ mode = 3;
+ } else {
+ if (groupLength < sizeof(groupBuffer) - 1) {
+ groupBuffer[groupLength + 0] = utf8Char;
+ groupBuffer[groupLength + 1] = 0;
+ groupLength++;
+ }
+ }
+ break;
}
}
language->num_strings = maxStringId + 1;
@@ -311,21 +349,6 @@ static void language_close(language_data *language)
language->string_data_size = 0;
}
-const int OpenRCT2LangIdToObjectLangId[] = {
- 0,
- 0,
- 1,
- 3,
- 6,
- 2,
- 0,
- 0,
- 4,
- 7,
- 5,
- 13
-};
-
#define STEX_BASE_STRING_ID 3447
#define NONSTEX_BASE_STRING_ID 3463
#define MAX_OBJECT_CACHED_STRINGS 2048
@@ -420,7 +443,7 @@ rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/
char *pString = NULL;
int result = 0;
bool isBlank;
-
+
while ((languageId = *(*pStringTable)++) != RCT2_LANGUAGE_ID_END) {
isBlank = true;
@@ -461,6 +484,23 @@ rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/
while (*(*pStringTable)++ != 0);
}
+ if (RCT2_GLOBAL(0x009ADAFC, uint8) == 0) {
+ if (type == OBJECT_TYPE_RIDE) {
+ char name[9];
+ memcpy(name, object_entry_groups[type].entries[index].name, 8);
+ name[8] = 0;
+
+ if (strcmp(name, "MGR1 ") == 0) {
+ switch (tableindex) {
+ case 0: return 824;
+ case 1: return 2142;
+ }
+ }
+ }
+ } else {
+
+ }
+
// If not scenario text
if (RCT2_GLOBAL(0x009ADAFC, uint8) == 0) {
int stringid = NONSTEX_BASE_STRING_ID;