From 8ec51b6fc12775b9a9f1c5f8c1becb1f95fd8104 Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Sat, 29 Aug 2015 02:05:21 +0100 Subject: [PATCH 01/10] add FileStream, IStream and IDisposable --- projects/openrct2.vcxproj | 4 ++ projects/openrct2.vcxproj.filters | 15 +++++ src/common.h | 2 + src/core/Exception.hpp | 10 ++++ src/core/FileStream.hpp | 94 +++++++++++++++++++++++++++++++ src/core/IDisposable.hpp | 10 ++++ src/core/IStream.hpp | 75 ++++++++++++++++++++++++ 7 files changed, 210 insertions(+) create mode 100644 src/core/Exception.hpp create mode 100644 src/core/FileStream.hpp create mode 100644 src/core/IDisposable.hpp create mode 100644 src/core/IStream.hpp diff --git a/projects/openrct2.vcxproj b/projects/openrct2.vcxproj index c5b0a22447..67022bd317 100644 --- a/projects/openrct2.vcxproj +++ b/projects/openrct2.vcxproj @@ -203,6 +203,10 @@ + + + + diff --git a/projects/openrct2.vcxproj.filters b/projects/openrct2.vcxproj.filters index 9c13072575..b82c90de10 100644 --- a/projects/openrct2.vcxproj.filters +++ b/projects/openrct2.vcxproj.filters @@ -53,6 +53,9 @@ {c6b9c169-ff2a-41df-9b1c-47d15763c3e2} + + {28a808eb-9017-44cc-939b-f828fd1e2e7d} + @@ -773,5 +776,17 @@ Source\Interface + + Source\Core + + + Source\Core + + + Source\Core + + + Source\Core + \ No newline at end of file diff --git a/src/common.h b/src/common.h index b8c710cf63..113f5850e5 100644 --- a/src/common.h +++ b/src/common.h @@ -26,4 +26,6 @@ #define SafeFree(x) if ((x) != NULL) { free(x); (x) = NULL; } +#define interface struct + #endif \ No newline at end of file diff --git a/src/core/Exception.hpp b/src/core/Exception.hpp new file mode 100644 index 0000000000..f3daf469e6 --- /dev/null +++ b/src/core/Exception.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include +#include "../common.h" + +class Exception : public std::exception { +public: + Exception() : std::exception() { } + Exception(const char *message) : std::exception(message) { } +}; diff --git a/src/core/FileStream.hpp b/src/core/FileStream.hpp new file mode 100644 index 0000000000..c70dfc90fa --- /dev/null +++ b/src/core/FileStream.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include +#include "../common.h" +#include "IStream.hpp" + +enum { + FILE_MODE_OPEN, + FILE_MODE_WRITE +}; + +/** + * A stream for reading and writing to files. Wraps an SDL_RWops, SDL2's cross platform file stream. + */ +class FileStream : public IStream { +private: + SDL_RWops *_file; + bool _canRead; + bool _canWrite; + bool _disposed; + +public: + FileStream(const utf8 *path, int fileMode) { + const char *mode; + switch (fileMode) { + case FILE_MODE_OPEN: + mode = "rb"; + _canRead = true; + _canWrite = false; + break; + case FILE_MODE_WRITE: + mode = "wb"; + _canRead = false; + _canWrite = true; + break; + default: + throw; + } + + _file = SDL_RWFromFile(path, mode); + if (_file == NULL) { + throw IOException(SDL_GetError()); + } + + _disposed = false; + } + + ~FileStream() { + Dispose(); + } + + void Dispose() override { + if (!_disposed) { + _disposed = true; + SDL_RWclose(_file); + } + } + + bool CanRead() const override { return _canRead; } + bool CanWrite() const override { return _canWrite; } + + sint64 GetLength() const override { return SDL_RWsize(_file); } + sint64 GetPosition() const override { return SDL_RWtell(_file); } + + void SetPosition(sint64 position) override { + Seek(position, STREAM_SEEK_BEGIN); + } + + void Seek(sint64 offset, int origin) override { + switch (origin) { + case STREAM_SEEK_BEGIN: + SDL_RWseek(_file, offset, RW_SEEK_SET); + break; + case STREAM_SEEK_CURRENT: + SDL_RWseek(_file, offset, RW_SEEK_CUR); + break; + case STREAM_SEEK_END: + SDL_RWseek(_file, offset, RW_SEEK_END); + break; + } + } + + void Read(void *buffer, int length) override { + if (SDL_RWread(_file, buffer, length, 1) != 1) { + throw IOException("Attempted to read past end of file."); + } + } + + void Write(const void *buffer, int length) override { + if (SDL_RWwrite(_file, buffer, length, 1) != 1) { + throw IOException("Unable to write to file."); + } + } +}; diff --git a/src/core/IDisposable.hpp b/src/core/IDisposable.hpp new file mode 100644 index 0000000000..1b30559b03 --- /dev/null +++ b/src/core/IDisposable.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "../common.h" + +/** +* Represents an object that can be disposed. So things can explicitly close resources before the destructor kicks in. +*/ +interface IDisposable abstract { + virtual void Dispose() abstract; +}; diff --git a/src/core/IStream.hpp b/src/core/IStream.hpp new file mode 100644 index 0000000000..bc3bb1e01c --- /dev/null +++ b/src/core/IStream.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "../common.h" +#include "Exception.hpp" +#include "IDisposable.hpp" + +enum { + STREAM_SEEK_BEGIN, + STREAM_SEEK_CURRENT, + STREAM_SEEK_END +}; + +/** + * Represents a stream that can be read or written to. Implemented by types such as FileStream, NetworkStream or MemoryStream. + */ +interface IStream abstract : public IDisposable { + /////////////////////////////////////////////////////////////////////////// + // Interface methods + /////////////////////////////////////////////////////////////////////////// + // virtual ~IStream() abstract; + + virtual bool CanRead() const abstract; + virtual bool CanWrite() const abstract; + + virtual sint64 GetLength() const abstract; + virtual sint64 GetPosition() const abstract; + virtual void SetPosition(sint64 position) abstract; + virtual void Seek(sint64 offset, int origin) abstract; + + virtual void Read(void *buffer, int length) abstract; + virtual void Write(const void *buffer, int length) abstract; + + /////////////////////////////////////////////////////////////////////////// + // Helper methods + /////////////////////////////////////////////////////////////////////////// + + /** + * Reads the size of the given type from the stream directly into the given address. + */ + template + void Read(T *value) { + Read(value, sizeof(T)); + } + + /** + * Writes the size of the given type to the stream directly from the given address. + */ + template + void Write(const T *value) { + Write(value, sizeof(T)); + } + + /** + * Reads the given type from the stream. Use this only for small types (e.g. sint8, sint64, double) + */ + template + T ReadValue() { + T buffer; + Read(&buffer); + return buffer; + } + + /** + * Writes the given type to the stream. Use this only for small types (e.g. sint8, sint64, double) + */ + template + void WriteValue(const T value) { + Write(&value); + } +}; + +class IOException : public Exception { +public: + IOException(const char *message) : Exception(message) { } +}; From 952a4abdd1bc6f97efbde4921d842d54207fe4c9 Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Mon, 31 Aug 2015 23:06:11 +0100 Subject: [PATCH 02/10] implement DAT translation, part 1 --- projects/openrct2.vcxproj | 2 + projects/openrct2.vcxproj.filters | 6 + src/localisation/LanguagePack.cpp | 200 ++++++++++++++++++++++++++++++ src/localisation/LanguagePack.h | 94 ++++++++++++++ src/localisation/language.c | 96 +++++++++----- 5 files changed, 370 insertions(+), 28 deletions(-) create mode 100644 src/localisation/LanguagePack.cpp create mode 100644 src/localisation/LanguagePack.h 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; From 7a0478404efb34b973e2407c37a9cc298d09a3ee Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Wed, 2 Sep 2015 22:00:02 +0100 Subject: [PATCH 03/10] add new C++ memory utility functions --- projects/openrct2.vcxproj | 3 + projects/openrct2.vcxproj.filters | 9 +++ src/core/Exception.hpp | 2 + src/core/Memory.hpp | 64 +++++++++++++++++++++ src/core/StringBuilder.hpp | 82 +++++++++++++++++++++++++++ src/core/StringReader.hpp | 57 +++++++++++++++++++ src/localisation/LanguagePack.cpp | 93 +++++-------------------------- src/localisation/LanguagePack.h | 53 +----------------- 8 files changed, 233 insertions(+), 130 deletions(-) create mode 100644 src/core/Memory.hpp create mode 100644 src/core/StringBuilder.hpp create mode 100644 src/core/StringReader.hpp diff --git a/projects/openrct2.vcxproj b/projects/openrct2.vcxproj index 688622c8a5..bca15bf71e 100644 --- a/projects/openrct2.vcxproj +++ b/projects/openrct2.vcxproj @@ -208,6 +208,9 @@ + + + diff --git a/projects/openrct2.vcxproj.filters b/projects/openrct2.vcxproj.filters index 9cd193edaf..dad1e96ac8 100644 --- a/projects/openrct2.vcxproj.filters +++ b/projects/openrct2.vcxproj.filters @@ -794,5 +794,14 @@ Source\Localisation + + Source\Core + + + Source\Core + + + Source\Core + \ No newline at end of file diff --git a/src/core/Exception.hpp b/src/core/Exception.hpp index f3daf469e6..2940d578b0 100644 --- a/src/core/Exception.hpp +++ b/src/core/Exception.hpp @@ -7,4 +7,6 @@ class Exception : public std::exception { public: Exception() : std::exception() { } Exception(const char *message) : std::exception(message) { } + + const char *GetMessage() const { return what(); } }; diff --git a/src/core/Memory.hpp b/src/core/Memory.hpp new file mode 100644 index 0000000000..2e4d77c99a --- /dev/null +++ b/src/core/Memory.hpp @@ -0,0 +1,64 @@ +#pragma once + +/** + * Utility methods for memory management. Typically helpers and wrappers around the C standard library. + */ +namespace Memory { + template + T *Allocate() { + return (T*)malloc(sizeof(T)); + } + + template + T *Allocate(size_t size) { + return (T*)malloc(size); + } + + template + T *AllocateArray(size_t count) { + return (T*)malloc(count * sizeof(T)); + } + + template + T *Reallocate(T *ptr, size_t size) { + if (ptr == NULL) + return (T*)malloc(size); + else + return (T*)realloc((void*)ptr, size); + } + + template + T *ReallocateArray(T *ptr, size_t count) { + if (ptr == NULL) + return (T*)malloc(count * sizeof(T)); + else + return (T*)realloc((void*)ptr, count * sizeof(T)); + } + + template + void Free(T *ptr) { + free((void*)ptr); + } + + template + T *Copy(T *dst, const T *src, size_t size) { + return (T*)memcpy((void*)dst, (const void*)src, size); + } + + template + T *CopyArray(T *dst, const T *src, size_t count) { + return (T*)memcpy((void*)dst, (const void*)src, count * sizeof(T)); + } + + template + T *Duplicate(const T *src, size_t size) { + T *result = Allocate(size); + return Copy(result, src, size); + } + + template + T *DuplicateArray(const T *src, size_t count) { + T *result = AllocateArray(count); + return CopyArray(result, src, count); + } +} diff --git a/src/core/StringBuilder.hpp b/src/core/StringBuilder.hpp new file mode 100644 index 0000000000..251fd5ca5a --- /dev/null +++ b/src/core/StringBuilder.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include "../common.h" +#include "../localisation/localisation.h" +#include "Memory.hpp" + +/** + * Class for constructing strings efficiently. A buffer is automatically allocated and reallocated when characters or strings + * are appended. Use GetString to copy the current state of the string builder to a new fire and forget string. + */ +class StringBuilder final { +public: + StringBuilder() { + _buffer = NULL; + _capacity = 0; + _length = 0; + } + + StringBuilder(int capacity) : StringBuilder() { + EnsureCapacity(capacity); + } + + ~StringBuilder() { + if (_buffer != NULL) Memory::Free(_buffer); + } + + utf8 *GetString() const { + utf8 *result = Memory::AllocateArray(_length + 1); + Memory::CopyArray(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; + _buffer[_length] = 0; + } + + void Append(utf8 *text) { + int textLength = strlen(text); + + EnsureCapacity(_length + textLength + 1); + Memory::Copy(_buffer + _length, text, textLength); + _length += textLength; + _buffer[_length] = 0; + } + + void Clear() { + _length = 0; + if (_capacity >= 1) { + _buffer[_length] = 0; + } + } + + /** + * Gets the current state of the StringBuilder. Warning: this represents the StringBuilder's current working buffer and will + * be deallocated when the StringBuilder is destructed. + */ + const utf8 *GetBuffer() { + return _buffer; + } + +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; + } + + _buffer = Memory::ReallocateArray(_buffer, _capacity); + } +}; diff --git a/src/core/StringReader.hpp b/src/core/StringReader.hpp new file mode 100644 index 0000000000..095f7bd285 --- /dev/null +++ b/src/core/StringReader.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include "../common.h" +#include "../localisation/localisation.h" +#include "../util/util.h" + +interface IStringReader abstract { + virtual bool TryPeek(int *outCodepoint) abstract; + virtual bool TryRead(int *outCodepoint) abstract; + virtual void Skip() abstract; +}; + +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; +}; diff --git a/src/localisation/LanguagePack.cpp b/src/localisation/LanguagePack.cpp index a9deaa3781..ecfe0e934e 100644 --- a/src/localisation/LanguagePack.cpp +++ b/src/localisation/LanguagePack.cpp @@ -4,100 +4,37 @@ extern "C" { #include "localisation.h" } +#include "../core/FileStream.hpp" +#include "../core/Memory.hpp" +#include "../core/StringBuilder.hpp" #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; + uint32 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); + try { + FileStream fs = FileStream(path, FILE_MODE_OPEN); + + fileLength = (uint32)fs.GetLength(); + fileData = Memory::Allocate(fileLength); + fs.Read(fileData, fileLength); + + fs.Dispose(); + } catch (Exception ex) { + log_error("Unable to open %s: %s", path, ex.GetMessage()); 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); + Memory::Free(fileData); return result; } diff --git a/src/localisation/LanguagePack.h b/src/localisation/LanguagePack.h index a66bc02093..10b4c29d8b 100644 --- a/src/localisation/LanguagePack.h +++ b/src/localisation/LanguagePack.h @@ -8,58 +8,7 @@ extern "C" { #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; -}; +#include "../core/StringReader.hpp" class LanguagePack final { public: From 8085abadd33d4837b692b8e4e78f8754eb66f5f1 Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Fri, 4 Sep 2015 23:27:47 +0100 Subject: [PATCH 04/10] add basic support for localisation object string overriding --- projects/openrct2.vcxproj | 2 +- projects/openrct2.vcxproj.filters | 6 +- src/common.h | 4 + src/core/Exception.hpp | 11 +- src/core/IDisposable.hpp | 6 +- src/core/IStream.hpp | 2 +- src/core/StringBuilder.hpp | 53 +++- src/core/StringReader.hpp | 19 +- src/localisation/LanguagePack.cpp | 269 ++++++++++++++++-- src/localisation/LanguagePack.h | 19 +- src/localisation/{language.c => language.cpp} | 253 +++------------- 11 files changed, 384 insertions(+), 260 deletions(-) rename src/localisation/{language.c => language.cpp} (62%) diff --git a/projects/openrct2.vcxproj b/projects/openrct2.vcxproj index bca15bf71e..5e66683591 100644 --- a/projects/openrct2.vcxproj +++ b/projects/openrct2.vcxproj @@ -64,7 +64,7 @@ - + diff --git a/projects/openrct2.vcxproj.filters b/projects/openrct2.vcxproj.filters index dad1e96ac8..cd83b092d9 100644 --- a/projects/openrct2.vcxproj.filters +++ b/projects/openrct2.vcxproj.filters @@ -216,9 +216,6 @@ Source\Localisation - - Source\Localisation - Source\Ride @@ -540,6 +537,9 @@ Source\Localisation + + Source\Localisation + diff --git a/src/common.h b/src/common.h index 113f5850e5..4ea6488767 100644 --- a/src/common.h +++ b/src/common.h @@ -26,6 +26,10 @@ #define SafeFree(x) if ((x) != NULL) { free(x); (x) = NULL; } +#define SafeDelete(x) if ((x) != nullptr) { delete (x); (x) = nullptr; } +#define SafeDeleteArray(x) if ((x) != nullptr) { delete[] (x); (x) = nullptr; } + #define interface struct +#define abstract = 0 #endif \ No newline at end of file diff --git a/src/core/Exception.hpp b/src/core/Exception.hpp index 2940d578b0..ac3d25d2ad 100644 --- a/src/core/Exception.hpp +++ b/src/core/Exception.hpp @@ -6,7 +6,14 @@ class Exception : public std::exception { public: Exception() : std::exception() { } - Exception(const char *message) : std::exception(message) { } + Exception(const char *message) : std::exception() { + _message = message; + } + virtual ~Exception() { } - const char *GetMessage() const { return what(); } + const char *what() const throw() override { return _message; } + const char *GetMessage() const { return _message; } + +private: + const char *_message; }; diff --git a/src/core/IDisposable.hpp b/src/core/IDisposable.hpp index 1b30559b03..e0cfb58726 100644 --- a/src/core/IDisposable.hpp +++ b/src/core/IDisposable.hpp @@ -3,8 +3,8 @@ #include "../common.h" /** -* Represents an object that can be disposed. So things can explicitly close resources before the destructor kicks in. -*/ -interface IDisposable abstract { + * Represents an object that can be disposed. So things can explicitly close resources before the destructor kicks in. + */ +interface IDisposable { virtual void Dispose() abstract; }; diff --git a/src/core/IStream.hpp b/src/core/IStream.hpp index bc3bb1e01c..00911cc6aa 100644 --- a/src/core/IStream.hpp +++ b/src/core/IStream.hpp @@ -13,7 +13,7 @@ enum { /** * Represents a stream that can be read or written to. Implemented by types such as FileStream, NetworkStream or MemoryStream. */ -interface IStream abstract : public IDisposable { +interface IStream : public IDisposable { /////////////////////////////////////////////////////////////////////////// // Interface methods /////////////////////////////////////////////////////////////////////////// diff --git a/src/core/StringBuilder.hpp b/src/core/StringBuilder.hpp index 251fd5ca5a..2618a3d882 100644 --- a/src/core/StringBuilder.hpp +++ b/src/core/StringBuilder.hpp @@ -6,7 +6,7 @@ /** * Class for constructing strings efficiently. A buffer is automatically allocated and reallocated when characters or strings - * are appended. Use GetString to copy the current state of the string builder to a new fire and forget string. + * are appended. Use GetString to copy the current state of the string builder to a new fire-and-forget string. */ class StringBuilder final { public: @@ -24,13 +24,9 @@ public: if (_buffer != NULL) Memory::Free(_buffer); } - utf8 *GetString() const { - utf8 *result = Memory::AllocateArray(_length + 1); - Memory::CopyArray(result, _buffer, _length); - result[_length] = 0; - return result; - } - + /** + * Appends the given character to the current string. + */ void Append(int codepoint) { int codepointLength = utf8_get_codepoint_length(codepoint); EnsureCapacity(_length + codepointLength + 1); @@ -39,7 +35,10 @@ public: _buffer[_length] = 0; } - void Append(utf8 *text) { + /** + * Appends the given string to the current string. + */ + void Append(const utf8 *text) { int textLength = strlen(text); EnsureCapacity(_length + textLength + 1); @@ -48,6 +47,9 @@ public: _buffer[_length] = 0; } + /** + * Clears the current string, but preserves the allocated memory for another string. + */ void Clear() { _length = 0; if (_capacity >= 1) { @@ -55,14 +57,45 @@ public: } } + /** + * Like Clear, only will guarantee freeing of the underlying buffer. + */ + void Reset() { + _length = 0; + _capacity = 0; + if (_buffer != NULL) { + Memory::Free(_buffer); + } + } + + /** + * Returns the current string buffer as a new fire-and-forget string. + */ + utf8 *GetString() const { + utf8 *result = Memory::AllocateArray(_length + 1); + Memory::CopyArray(result, _buffer, _length); + result[_length] = 0; + return result; + } + /** * Gets the current state of the StringBuilder. Warning: this represents the StringBuilder's current working buffer and will * be deallocated when the StringBuilder is destructed. */ - const utf8 *GetBuffer() { + const utf8 *GetBuffer() const { return _buffer; } + /** + * Gets the amount of allocated memory for the string buffer. + */ + size_t GetCapacity() const { return _capacity; } + + /** + * Gets the length of the current string. + */ + size_t GetLength() const { return _length; } + private: utf8 *_buffer; size_t _capacity; diff --git a/src/core/StringReader.hpp b/src/core/StringReader.hpp index 095f7bd285..196470cc87 100644 --- a/src/core/StringReader.hpp +++ b/src/core/StringReader.hpp @@ -4,16 +4,16 @@ #include "../localisation/localisation.h" #include "../util/util.h" -interface IStringReader abstract { +interface IStringReader { virtual bool TryPeek(int *outCodepoint) abstract; virtual bool TryRead(int *outCodepoint) abstract; virtual void Skip() abstract; + virtual bool CanRead() const abstract; }; class UTF8StringReader final : public IStringReader { public: - UTF8StringReader(const utf8 *text) - { + UTF8StringReader(const utf8 *text) { // Skip UTF-8 byte order mark if (strlen(text) >= 3 && utf8_is_bom(text)) { text += 3; @@ -23,8 +23,7 @@ public: _current = text; } - bool TryPeek(int *outCodepoint) override - { + bool TryPeek(int *outCodepoint) override { if (_current == NULL) return false; int codepoint = utf8_get_next(_current, NULL); @@ -32,8 +31,7 @@ public: return true; } - bool TryRead(int *outCodepoint) override - { + bool TryRead(int *outCodepoint) override { if (_current == NULL) return false; int codepoint = utf8_get_next(_current, &_current); @@ -45,12 +43,15 @@ public: return true; } - void Skip() override - { + void Skip() override { int codepoint; TryRead(&codepoint); } + bool CanRead() const override { + return _current != NULL; + } + private: const utf8 *_text; const utf8 *_current; diff --git a/src/localisation/LanguagePack.cpp b/src/localisation/LanguagePack.cpp index ecfe0e934e..27513a4a58 100644 --- a/src/localisation/LanguagePack.cpp +++ b/src/localisation/LanguagePack.cpp @@ -10,6 +10,9 @@ extern "C" { #include "LanguagePack.h" #include +constexpr rct_string_id ObjectOverrideBase = 0x6000; +constexpr int ObjectOverrideMaxStringCount = 4; + LanguagePack *LanguagePack::FromFile(int id, const utf8 *path) { assert(path != NULL); @@ -50,9 +53,30 @@ LanguagePack::LanguagePack(int id, const utf8 *text) _id = id; _stringData = NULL; _currentGroup = NULL; + _currentObjectOverride = NULL; auto reader = UTF8StringReader(text); + while (reader.CanRead()) { + ParseLine(&reader); + } + _stringData = _stringDataSB.GetString(); + + size_t stringDataBaseAddress = (size_t)_stringData; + for (size_t i = 0; i < _strings.size(); i++) { + _strings[i] = (utf8*)(stringDataBaseAddress + (size_t)_strings[i]); + } + for (size_t i = 0; i < _objectOverrides.size(); i++) { + for (int j = 0; j < ObjectOverrideMaxStringCount; j++) { + const utf8 **strPtr = &(_objectOverrides[i].strings[j]); + if (*strPtr != NULL) { + *strPtr = (utf8*)(stringDataBaseAddress + (size_t)*strPtr); + } + } + } + + // Destruct the string builder to free memory + _stringDataSB = StringBuilder(); } LanguagePack::~LanguagePack() @@ -61,11 +85,100 @@ LanguagePack::~LanguagePack() SafeFree(_currentGroup); } +const utf8 *LanguagePack::GetString(int stringId) const { + if (stringId >= ObjectOverrideBase) { + int offset = stringId - ObjectOverrideBase; + int ooIndex = offset / ObjectOverrideMaxStringCount; + int ooStringIndex = offset % ObjectOverrideMaxStringCount; + + if (_objectOverrides.size() > (size_t)ooIndex) { + return _objectOverrides[ooIndex].strings[ooStringIndex]; + } else { + return NULL; + } + } else { + if (_strings.size() > (size_t)stringId) { + return _strings[stringId]; + } else { + return NULL; + } + } +} + +rct_string_id LanguagePack::GetObjectOverrideStringId(const char *objectIdentifier, int index) +{ + assert(objectIdentifier != NULL); + assert(index < ObjectOverrideMaxStringCount); + + int ooIndex = 0; + for (const ObjectOverride &objectOverride : _objectOverrides) { + if (strncmp(objectOverride.name, objectIdentifier, 8) == 0) { + if (objectOverride.strings[index] == NULL) { + return STR_NONE; + } + return ObjectOverrideBase + (ooIndex * ObjectOverrideMaxStringCount) + index; + } + ooIndex++; + } + + return STR_NONE; +} + +LanguagePack::ObjectOverride *LanguagePack::GetObjectOverride(const char *objectIdentifier) +{ + assert(objectIdentifier != NULL); + + for (size_t i = 0; i < _objectOverrides.size(); i++) { + ObjectOverride *oo = &_objectOverrides[i]; + if (strncmp(oo->name, objectIdentifier, 8) == 0) { + return oo; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Parsing +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Partial support to open a uncompiled language file which parses tokens and converts them to the corresponding character +// code. Due to resource strings (strings in scenarios and objects) being written to the original game's string table, +// get_string will use those if the same entry in the loaded language is empty. +// +// Unsure at how the original game decides which entries to write resource strings to, but this could affect adding new +// strings for the time being. Further investigation is required. +// +// When reading the language files, the STR_XXXX part is read and XXXX becomes the string id number. Everything after the colon +// and before the new line will be saved as the string. Tokens are written with inside curly braces {TOKEN}. +// Use # at the beginning of a line to leave a comment. +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static bool IsWhitespace(int codepoint) +{ + return codepoint == '\t' || codepoint == ' ' || codepoint == '\r' || codepoint == '\n'; +} + +static bool IsNewLine(int codepoint) +{ + return codepoint == '\r' || codepoint == '\n'; +} + static void SkipWhitespace(IStringReader *reader) { int codepoint; while (reader->TryPeek(&codepoint)) { - if (codepoint == '\t' || codepoint == ' ' || codepoint == '\r' || codepoint == '\n') { + if (IsWhitespace(codepoint)) { + reader->Skip(); + } else { + break; + } + } +} + +static void SkipNewLine(IStringReader *reader) +{ + int codepoint; + while (reader->TryPeek(&codepoint)) { + if (IsNewLine(codepoint)) { reader->Skip(); } else { break; @@ -90,19 +203,23 @@ void LanguagePack::ParseLine(IStringReader *reader) SkipWhitespace(reader); int codepoint; - if (!reader->TryPeek(&codepoint)) - return; - - switch (codepoint) { - case '#': + if (reader->TryPeek(&codepoint)) { + switch (codepoint) { + case '#': + SkipToEndOfLine(reader); + break; + case '[': + ParseGroup(reader); + break; + case '\r': + case '\n': + break; + default: + ParseString(reader); + break; + } SkipToEndOfLine(reader); - break; - case '[': - ParseGroup(reader); - break; - default: - ParseString(reader); - break; + SkipNewLine(reader); } } @@ -116,8 +233,10 @@ void LanguagePack::ParseGroup(IStringReader *reader) // Read string up to ] or line end bool closedCorrectly = false; - while (reader->TryRead(&codepoint)) { - if (codepoint == '\n' || codepoint == '\r') break; + while (reader->TryPeek(&codepoint)) { + if (IsNewLine(codepoint)) break; + + reader->Skip(); if (codepoint == ']') { closedCorrectly = true; break; @@ -127,11 +246,129 @@ void LanguagePack::ParseGroup(IStringReader *reader) if (closedCorrectly) { SafeFree(_currentGroup); - _currentGroup = sb.GetString(); + + while (sb.GetLength() < 8) { + sb.Append(' '); + } + if (sb.GetLength() == 8) { + _currentGroup = sb.GetString(); + _currentObjectOverride = GetObjectOverride(_currentGroup); + if (_currentObjectOverride == NULL) { + _objectOverrides.push_back(ObjectOverride()); + _currentObjectOverride = &_objectOverrides[_objectOverrides.size() - 1]; + memset(_currentObjectOverride, 0, sizeof(ObjectOverride)); + memcpy(_currentObjectOverride->name, _currentGroup, 8); + } + } } } void LanguagePack::ParseString(IStringReader *reader) { + auto sb = StringBuilder(); + int codepoint; + // Parse string identifier + while (reader->TryPeek(&codepoint)) { + if (IsNewLine(codepoint)) { + // Unexpected new line, ignore line entirely + return; + } else if (!IsWhitespace(codepoint) && codepoint != ':') { + reader->Skip(); + sb.Append(codepoint); + } else { + break; + } + } + + SkipWhitespace(reader); + + // Parse a colon + if (!reader->TryPeek(&codepoint) || codepoint != ':') { + // Expected a colon, ignore line entirely + return; + } + reader->Skip(); + + // Validate identifier + const utf8 *identifier = sb.GetBuffer(); + + int stringId; + if (_currentGroup == NULL) { + if (sscanf(identifier, "STR_%4d", &stringId) != 1) { + // Ignore line entirely + return; + } + } else { + if (strcmp(identifier, "STR_NAME") == 0) { stringId = 0; } + else if (strcmp(identifier, "STR_DESC") == 0) { stringId = 1; } + + else if (strcmp(identifier, "STR_SCNR") == 0) { stringId = 0; } + else if (strcmp(identifier, "STR_PARK") == 0) { stringId = 1; } + else if (strcmp(identifier, "STR_DTLS") == 0) { stringId = 2; } + else { + // Ignore line entirely + return; + } + } + + // Rest of the line is the actual string + sb.Clear(); + while (reader->TryPeek(&codepoint) && !IsNewLine(codepoint)) { + if (codepoint == '{') { + uint32 token; + if (!ParseToken(reader, &token)) { + // Syntax error or unknown token, ignore line entirely + return; + } else { + sb.Append((int)token); + } + } else { + reader->Skip(); + sb.Append(codepoint); + } + } + + // Append a null terminator for the benefit of the last string + _stringDataSB.Append(0); + + // Get the relative offset to the string (add the base offset when we extract the string properly) + utf8 *relativeOffset = (utf8*)_stringDataSB.GetLength(); + + if (_currentGroup == NULL) { + // Make sure the list is big enough to contain this string id + while (_strings.size() <= (size_t)stringId) { + _strings.push_back(NULL); + } + + _strings[stringId] = relativeOffset; + } else { + _currentObjectOverride->strings[stringId] = relativeOffset; + } + + _stringDataSB.Append(sb.GetBuffer()); +} + +bool LanguagePack::ParseToken(IStringReader *reader, uint32 *token) +{ + auto sb = StringBuilder(); + int codepoint; + + // Skip open brace + reader->Skip(); + + while (reader->TryPeek(&codepoint)) { + if (IsNewLine(codepoint)) return false; + if (IsWhitespace(codepoint)) return false; + + reader->Skip(); + + if (codepoint == '}') break; + + sb.Append(codepoint); + } + + const utf8 *tokenName = sb.GetBuffer(); + *token = format_get_code(tokenName); + return true; } diff --git a/src/localisation/LanguagePack.h b/src/localisation/LanguagePack.h index 10b4c29d8b..ba09b387a5 100644 --- a/src/localisation/LanguagePack.h +++ b/src/localisation/LanguagePack.h @@ -8,6 +8,7 @@ extern "C" { #include "localisation.h" } +#include "../core/StringBuilder.hpp" #include "../core/StringReader.hpp" class LanguagePack final { @@ -20,6 +21,16 @@ public: int GetId() const { return _id; } int GetCount() const { return _strings.size(); } + const utf8 *GetString(int stringId) const; + + void SetString(int stringId, const utf8 *str) { + if (_strings.size() >= (size_t)stringId) { + _strings[stringId] = str; + } + } + + rct_string_id GetObjectOverrideStringId(const char *objectIdentifier, int index); + private: struct ObjectOverride { char name[8]; @@ -28,16 +39,22 @@ private: int _id; utf8 *_stringData; - std::vector _strings; + std::vector _strings; + std::vector _objectOverrides; LanguagePack(int id, const utf8 *text); + ObjectOverride *GetObjectOverride(const char *objectIdentifier); /////////////////////////////////////////////////////////////////////////// // Parsing /////////////////////////////////////////////////////////////////////////// + StringBuilder _stringDataSB; utf8 *_currentGroup; + ObjectOverride *_currentObjectOverride; void ParseLine(IStringReader *reader); void ParseGroup(IStringReader *reader); void ParseString(IStringReader *reader); + + bool ParseToken(IStringReader *reader, uint32 *token); }; diff --git a/src/localisation/language.c b/src/localisation/language.cpp similarity index 62% rename from src/localisation/language.c rename to src/localisation/language.cpp index d2bc8557fe..071e6df8a6 100644 --- a/src/localisation/language.c +++ b/src/localisation/language.cpp @@ -18,6 +18,10 @@ * along with this program. If not, see . *****************************************************************************/ +#include "LanguagePack.h" + +extern "C" { + #include "../addresses.h" #include "../drawing/drawing.h" #include "../object.h" @@ -25,14 +29,6 @@ #include "../util/util.h" #include "localisation.h" -typedef struct { - int id; - int num_strings; - char **strings; - size_t string_data_size; - char *string_data; -} language_data; - enum { RCT2_LANGUAGE_ID_ENGLISH_UK, RCT2_LANGUAGE_ID_ENGLISH_US, @@ -88,25 +84,22 @@ const language_descriptor LanguagesDescriptors[LANGUAGE_COUNT] = { { "zh-Hant", "Chinese (Traditional)", "Chinese (Traditional)", "chinese_traditional", &TTFFontMingLiu, RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL }, // LANGUAGE_CHINESE_TRADITIONAL { "zh-Hans", "Chinese (Simplified)", "Chinese (Simplified)", "chinese_simplified", &TTFFontSimSun, RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED }, // LANGUAGE_CHINESE_SIMPLIFIED { "fi-FI", "Finnish", "Suomi", "finnish", FONT_OPENRCT2_SPRITE, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_FINNISH - { "kr-KR", "Korean", "Korean", "korean", &TTFFontMalgun, RCT2_LANGUAGE_ID_ENGLISH_UK }, // LANGUAGE_KOREAN + { "kr-KR", "Korean", "Korean", "korean", &TTFFontMalgun, RCT2_LANGUAGE_ID_KOREAN }, // LANGUAGE_KOREAN }; int gCurrentLanguage = LANGUAGE_UNDEFINED; bool gUseTrueTypeFont = false; -language_data _languageFallback = { 0 }; -language_data _languageCurrent = { 0 }; +LanguagePack *_languageFallback = nullptr; +LanguagePack *_languageCurrent = nullptr; -const char **_languageOriginal = (char**)0x009BF2D4; +const char **_languageOriginal = (const char**)0x009BF2D4; -const utf8 BlackUpArrowString[] = { 0xC2, 0x8E, 0xE2, 0x96, 0xB2, 0x00 }; -const utf8 BlackDownArrowString[] = { 0xC2, 0x8E, 0xE2, 0x96, 0xBC, 0x00 }; -const utf8 BlackLeftArrowString[] = { 0xC2, 0x8E, 0xE2, 0x97, 0x80, 0x00 }; -const utf8 BlackRightArrowString[] = { 0xC2, 0x8E, 0xE2, 0x96, 0xB6, 0x00 }; -const utf8 CheckBoxMarkString[] = { 0xE2, 0x9C, 0x93, 0x00 }; - -static int language_open_file(const utf8 *filename, language_data *language); -static void language_close(language_data *language); +const utf8 BlackUpArrowString[] = { (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x96, (utf8)0xB2, (utf8)0x00 }; +const utf8 BlackDownArrowString[] = { (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x96, (utf8)0xBC, (utf8)0x00 }; +const utf8 BlackLeftArrowString[] = { (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x97, (utf8)0x80, (utf8)0x00 }; +const utf8 BlackRightArrowString[] = { (utf8)0xC2, (utf8)0x8E, (utf8)0xE2, (utf8)0x96, (utf8)0xB6, (utf8)0x00 }; +const utf8 CheckBoxMarkString[] = { (utf8)0xE2, (utf8)0x9C, (utf8)0x93, (utf8)0x00 }; void utf8_remove_format_codes(utf8 *text) { @@ -128,10 +121,10 @@ const char *language_get_string(rct_string_id id) if (id == (rct_string_id)STR_NONE) return NULL; - if (_languageCurrent.num_strings > id) - openrctString = _languageCurrent.strings[id]; - if (openrctString == NULL && _languageFallback.num_strings > id) - openrctString = _languageFallback.strings[id]; + if (_languageCurrent != nullptr) + openrctString = _languageCurrent->GetString(id); + if (openrctString == NULL && _languageFallback != nullptr) + openrctString = _languageFallback->GetString(id); if (id >= STR_OPENRCT2_BEGIN_STRING_ID) { return openrctString != NULL ? openrctString : "(undefined string)"; @@ -153,14 +146,12 @@ int language_open(int id) if (id != LANGUAGE_ENGLISH_UK) { sprintf(filename, languagePath, gExePath, LanguagesDescriptors[LANGUAGE_ENGLISH_UK].path); - if (language_open_file(filename, &_languageFallback)) { - _languageFallback.id = LANGUAGE_ENGLISH_UK; - } + _languageFallback = LanguagePack::FromFile(LANGUAGE_ENGLISH_UK, filename); } sprintf(filename, languagePath, gExePath, LanguagesDescriptors[id].path); - if (language_open_file(filename, &_languageCurrent)) { - _languageCurrent.id = id; + _languageCurrent = LanguagePack::FromFile(id, filename); + if (_languageCurrent != NULL) { gCurrentLanguage = id; if (LanguagesDescriptors[id].font == FONT_OPENRCT2_SPRITE) { @@ -184,171 +175,11 @@ int language_open(int id) void language_close_all() { - language_close(&_languageFallback); - language_close(&_languageCurrent); - _languageFallback.id = LANGUAGE_UNDEFINED; - _languageCurrent.id = LANGUAGE_UNDEFINED; + SafeDelete(_languageFallback); + SafeDelete(_languageCurrent); gCurrentLanguage = LANGUAGE_UNDEFINED; } -/** - * Partial support to open a uncompiled language file which parses tokens and converts them to the corresponding character - * code. Due to resource strings (strings in scenarios and objects) being written to the original game's string table, - * get_string will use those if the same entry in the loaded language is empty. - * - * Unsure at how the original game decides which entries to write resource strings to, but this could affect adding new - * strings for the time being. Further investigation is required. - * - * Also note that all strings are currently still ASCII. It probably can't be converted to UTF-8 until all game functions that - * read / write strings in some way is decompiled. The original game used a DIY extended 8-bit extended ASCII set for special - * characters, format codes and accents. - * - * In terms of reading the language files, the STR_XXXX part is read and XXXX becomes the string id number. Everything after the - * colon and before the new line will be saved as the string. Tokens are written with inside curly braces {TOKEN}. - * Use # at the beginning of a line to leave a comment. - */ -static int language_open_file(const utf8 *filename, language_data *language) -{ - assert(filename != NULL); - assert(language != NULL); - - SDL_RWops *f = SDL_RWFromFile(filename, "rb"); - if (f == NULL) - return 0; - - SDL_RWseek(f, 0, RW_SEEK_END); - language->string_data_size = (size_t)(SDL_RWtell(f) + 1); - language->string_data = calloc(1, language->string_data_size); - SDL_RWseek(f, 0, RW_SEEK_SET); - SDL_RWread(f, language->string_data, language->string_data_size, 1); - SDL_RWclose(f); - - language->strings = calloc(STR_COUNT, sizeof(char*)); - - 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; - - // Skim UTF-8 byte order mark - if (utf8_is_bom(language->string_data)) - i += 3; - - for (; i < language->string_data_size; i++) { - char *src = &language->string_data[i]; - - // Handle UTF-8 - char *srcNext; - uint32 utf8Char = utf8_get_next(src, (const utf8**)&srcNext); - i += srcNext - src - 1; - - switch (mode) { - case 0: - // Search for a comment - if (utf8Char == '#') { - mode = 3; - } 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; - case 1: - // Copy string over, stop at line break - if (utf8Char == '{') { - token = src + 1; - mode = 2; - } else if (utf8Char == '\n' || *src == '\r') { - *dst = 0; - mode = 0; - } else { - dst = utf8_write_codepoint(dst, utf8Char); - } - break; - case 2: - // Read token, convert to code - if (utf8Char == '}') { - int tokenLength = min(src - token, sizeof(tokenBuffer) - 1); - memcpy(tokenBuffer, token, tokenLength); - tokenBuffer[tokenLength] = 0; - uint32 code = format_get_code(tokenBuffer); - if (code == 0) { - code = atoi(tokenBuffer); - *dst++ = code & 0xFF; - } else { - dst = utf8_write_codepoint(dst, code); - } - mode = 1; - } - break; - case 3: - 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; - language->strings = realloc(language->strings, language->num_strings * sizeof(char*)); - - return 1; -} - -static void language_close(language_data *language) -{ - SafeFree(language->strings); - SafeFree(language->string_data); - language->num_strings = 0; - language->string_data_size = 0; -} - #define STEX_BASE_STRING_ID 3447 #define NONSTEX_BASE_STRING_ID 3463 #define MAX_OBJECT_CACHED_STRINGS 2048 @@ -402,9 +233,9 @@ static wchar_t convert_specific_language_character_to_unicode(int languageId, wc static utf8 *convert_multibyte_charset(const char *src, int languageId) { int reservedLength = (strlen(src) * 4) + 1; - utf8 *buffer = malloc(reservedLength); + utf8 *buffer = (utf8*)malloc(reservedLength); utf8 *dst = buffer; - for (const uint8 *ch = src; *ch != 0;) { + for (const uint8 *ch = (const uint8*)src; *ch != 0;) { if (*ch == 0xFF) { ch++; uint8 a = *ch++; @@ -419,7 +250,7 @@ static utf8 *convert_multibyte_charset(const char *src, int languageId) } *dst++ = 0; int actualLength = dst - buffer; - buffer = realloc(buffer, actualLength); + buffer = (utf8*)realloc(buffer, actualLength); return buffer; } @@ -450,7 +281,7 @@ rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/ // Strings that are just ' ' are set as invalid langauges. // But if there is no real string then it will set the string as // the blank string - for (char *ch = *pStringTable; *ch != 0; ch++) { + for (char *ch = (char*)(*pStringTable); *ch != 0; ch++) { if (!isblank(*ch)) { isBlank = false; break; @@ -462,21 +293,21 @@ rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/ // This is the ideal situation. Language found if (languageId == LanguagesDescriptors[gCurrentLanguage].rct2_original_id) { chosenLanguageId = languageId; - pString = *pStringTable; + pString = (char*)(*pStringTable); result |= 1; } // Just in case always load english into pString if (languageId == RCT2_LANGUAGE_ID_ENGLISH_UK && !(result & 1)) { chosenLanguageId = languageId; - pString = *pStringTable; + pString = (char*)(*pStringTable); result |= 2; } // Failing that fall back to whatever is first string if (!(result & 7)) { chosenLanguageId = languageId; - pString = *pStringTable; + pString = (char*)(*pStringTable); if (!isBlank) result |= 4; } @@ -485,20 +316,14 @@ rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/ } 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; + 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; - } - } + rct_string_id stringId = _languageCurrent->GetObjectOverrideStringId(name, tableindex); + if (stringId != (rct_string_id)STR_NONE) { + return stringId; } - } else { - } // If not scenario text @@ -528,8 +353,7 @@ rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/ utf8_trim_string(*cacheString); //put pointer in stringtable - if (_languageCurrent.num_strings > stringid) - _languageCurrent.strings[stringid] = *cacheString; + _languageCurrent->SetString(stringid, *cacheString); // Until all string related functions are finished copy // to old array as well. _languageOriginal[stringid] = *cacheString; @@ -551,11 +375,12 @@ rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/ utf8_trim_string(*cacheString); //put pointer in stringtable - if (_languageCurrent.num_strings > stringid) - _languageCurrent.strings[stringid] = *cacheString; + _languageCurrent->SetString(stringid, *cacheString); // Until all string related functions are finished copy // to old array as well. _languageOriginal[stringid] = *cacheString; return stringid; } } + +} From 34cd6a92426691b0b382d6dd716cc0f0f1451856 Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Sat, 5 Sep 2015 01:02:08 +0100 Subject: [PATCH 05/10] fix GCC compile issue --- src/core/StringBuilder.hpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/StringBuilder.hpp b/src/core/StringBuilder.hpp index 2618a3d882..46d9572455 100644 --- a/src/core/StringBuilder.hpp +++ b/src/core/StringBuilder.hpp @@ -1,5 +1,7 @@ #pragma once +#include + #include "../common.h" #include "../localisation/localisation.h" #include "Memory.hpp" @@ -105,7 +107,7 @@ private: { if (_capacity > capacity) return; - _capacity = max(8, _capacity); + _capacity = (std::max)(8, _capacity); while (_capacity < capacity) { _capacity *= 2; } From fe7879f0a3fdd7bce8e334e1d401452c252913a4 Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Sun, 6 Sep 2015 11:04:55 +0100 Subject: [PATCH 06/10] add capacity overriding --- src/localisation/LanguagePack.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/localisation/LanguagePack.cpp b/src/localisation/LanguagePack.cpp index 27513a4a58..e2a9ca60ad 100644 --- a/src/localisation/LanguagePack.cpp +++ b/src/localisation/LanguagePack.cpp @@ -302,6 +302,7 @@ void LanguagePack::ParseString(IStringReader *reader) } else { if (strcmp(identifier, "STR_NAME") == 0) { stringId = 0; } else if (strcmp(identifier, "STR_DESC") == 0) { stringId = 1; } + else if (strcmp(identifier, "STR_CPTY") == 0) { stringId = 2; } else if (strcmp(identifier, "STR_SCNR") == 0) { stringId = 0; } else if (strcmp(identifier, "STR_PARK") == 0) { stringId = 1; } From fc65a92275215b38b3d4669cb60b3815209a0020 Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Mon, 7 Sep 2015 22:05:36 +0100 Subject: [PATCH 07/10] fix object override strings for scenario editor and plugin.dat --- projects/openrct2.vcxproj | 1 + projects/openrct2.vcxproj.filters | 3 +++ src/core/Math.hpp | 23 +++++++++++++++++++ src/core/StringBuilder.hpp | 5 ++-- src/localisation/language.cpp | 14 +++++++----- src/object.c | 3 +++ src/object.h | 1 + src/object_list.c | 38 ++++++++++++++++++++++++++++++- 8 files changed, 78 insertions(+), 10 deletions(-) create mode 100644 src/core/Math.hpp diff --git a/projects/openrct2.vcxproj b/projects/openrct2.vcxproj index 5e66683591..cd66b434b3 100644 --- a/projects/openrct2.vcxproj +++ b/projects/openrct2.vcxproj @@ -208,6 +208,7 @@ + diff --git a/projects/openrct2.vcxproj.filters b/projects/openrct2.vcxproj.filters index cd83b092d9..070539ff77 100644 --- a/projects/openrct2.vcxproj.filters +++ b/projects/openrct2.vcxproj.filters @@ -803,5 +803,8 @@ Source\Core + + Source\Core + \ No newline at end of file diff --git a/src/core/Math.hpp b/src/core/Math.hpp new file mode 100644 index 0000000000..1e5902c124 --- /dev/null +++ b/src/core/Math.hpp @@ -0,0 +1,23 @@ +#pragma once + +/** + * Common mathematical functions. + */ +namespace Math { + + template + T Min(T a, T b) { + return a < b ? a : b; + } + + template + T Max(T a, T b) { + return a > b ? a : b; + } + + template + T Clamp(T low, T x, T max) { + return Min(Max(low, x), high); + } + +} diff --git a/src/core/StringBuilder.hpp b/src/core/StringBuilder.hpp index 46d9572455..b96c1cbe89 100644 --- a/src/core/StringBuilder.hpp +++ b/src/core/StringBuilder.hpp @@ -1,9 +1,8 @@ #pragma once -#include - #include "../common.h" #include "../localisation/localisation.h" +#include "Math.hpp" #include "Memory.hpp" /** @@ -107,7 +106,7 @@ private: { if (_capacity > capacity) return; - _capacity = (std::max)(8, _capacity); + _capacity = Math::Max(8U, _capacity); while (_capacity < capacity) { _capacity *= 2; } diff --git a/src/localisation/language.cpp b/src/localisation/language.cpp index 071e6df8a6..1f7904072f 100644 --- a/src/localisation/language.cpp +++ b/src/localisation/language.cpp @@ -315,15 +315,17 @@ rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/ while (*(*pStringTable)++ != 0); } + char name[9]; if (RCT2_GLOBAL(0x009ADAFC, uint8) == 0) { - char name[9]; memcpy(name, object_entry_groups[type].entries[index].name, 8); - name[8] = 0; + } else { + memcpy(name, gTempObjectLoadName, 8); + } + name[8] = 0; - rct_string_id stringId = _languageCurrent->GetObjectOverrideStringId(name, tableindex); - if (stringId != (rct_string_id)STR_NONE) { - return stringId; - } + rct_string_id stringId = _languageCurrent->GetObjectOverrideStringId(name, tableindex); + if (stringId != (rct_string_id)STR_NONE) { + return stringId; } // If not scenario text diff --git a/src/object.c b/src/object.c index c5d7af1f37..4295cbb146 100644 --- a/src/object.c +++ b/src/object.c @@ -33,6 +33,8 @@ #include "scenario.h" #include "rct1.h" +char gTempObjectLoadName[9] = { 0 }; + int object_load_entry(const utf8 *path, rct_object_entry *outEntry) { SDL_RWops *file; @@ -1566,6 +1568,7 @@ int object_get_scenario_text(rct_object_entry *entry) // Tell text to be loaded into a different address RCT2_GLOBAL(0x009ADAFC, uint8) = 255; + memcpy(gTempObjectLoadName, openedEntry.name, 8); // Not used?? RCT2_GLOBAL(0x009ADAFD, uint8) = 1; object_paint(openedEntry.flags & 0x0F, 0, 0, 0, 0, (int)chunk, 0, 0); diff --git a/src/object.h b/src/object.h index ad7d7c66bd..18c11087d9 100644 --- a/src/object.h +++ b/src/object.h @@ -91,6 +91,7 @@ typedef struct { } rct_object_filters; extern rct_object_entry_group object_entry_groups[]; +extern char gTempObjectLoadName[9]; int object_load_entry(const utf8 *path, rct_object_entry *outEntry); void object_list_load(); diff --git a/src/object_list.c b/src/object_list.c index 1e96973ee5..7fd147949c 100644 --- a/src/object_list.c +++ b/src/object_list.c @@ -27,6 +27,10 @@ #include "util/sawyercoding.h" #include "game.h" #include "rct1.h" +#include "world/entrance.h" +#include "world/footpath.h" +#include "world/scenery.h" +#include "world/water.h" #define OBJECT_ENTRY_GROUP_COUNT 11 #define OBJECT_ENTRY_COUNT 721 @@ -673,6 +677,33 @@ rct_object_entry *object_list_find(rct_object_entry *entry) return NULL; } +rct_string_id object_get_name_string_id(rct_object_entry *entry, const void *chunk) +{ + int objectType = entry->flags & 0x0F; + switch (objectType) { + case OBJECT_TYPE_RIDE: + return ((rct_ride_type*)chunk)->name; + case OBJECT_TYPE_SMALL_SCENERY: + case OBJECT_TYPE_LARGE_SCENERY: + case OBJECT_TYPE_WALLS: + case OBJECT_TYPE_BANNERS: + case OBJECT_TYPE_PATH_BITS: + return ((rct_scenery_entry*)chunk)->name; + case OBJECT_TYPE_PATHS: + return ((rct_path_type*)chunk)->string_idx; + case OBJECT_TYPE_SCENERY_SETS: + return ((rct_scenery_set_entry*)chunk)->name; + case OBJECT_TYPE_PARK_ENTRANCE: + return ((rct_entrance_type*)chunk)->string_idx; + case OBJECT_TYPE_WATER: + return ((rct_water_type*)chunk)->string_idx; + case OBJECT_TYPE_SCENARIO_TEXT: + return ((rct_stex_entry*)chunk)->scenario_name; + default: + return STR_NONE; + } +} + /* Installs an object_entry at the desired installed_entry address * Returns the size of the new entry. Will return 0 on failure. */ @@ -740,7 +771,12 @@ static uint32 install_object_entry(rct_object_entry* entry, rct_object_entry* in load_object_filter(entry, chunk, filter); // Always extract only the vehicle type, since the track type is always displayed in the left column, to prevent duplicate track names. - strcpy(installed_entry_pointer, language_get_string((rct_string_id)RCT2_GLOBAL(RCT2_ADDRESS_CURR_OBJECT_BASE_STRING_ID, uint32))); + rct_string_id nameStringId = object_get_name_string_id(entry, chunk); + if (nameStringId == STR_NONE) { + nameStringId = (rct_string_id)RCT2_GLOBAL(RCT2_ADDRESS_CURR_OBJECT_BASE_STRING_ID, uint32); + } + + strcpy(installed_entry_pointer, language_get_string(nameStringId)); while (*installed_entry_pointer++); // This is deceptive. Due to setting the total no images earlier to 0xF26E From 188978936b9b22d7584a1e05f7b4ef9ecfae1a8b Mon Sep 17 00:00:00 2001 From: Gymnasiast Date: Tue, 8 Sep 2015 11:44:55 +0200 Subject: [PATCH 08/10] Fix Math.Clamp definition --- src/core/Math.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/Math.hpp b/src/core/Math.hpp index 1e5902c124..939159db08 100644 --- a/src/core/Math.hpp +++ b/src/core/Math.hpp @@ -16,7 +16,7 @@ namespace Math { } template - T Clamp(T low, T x, T max) { + T Clamp(T low, T x, T high) { return Min(Max(low, x), high); } From fddf057d6874b69c747d866553d87b039f611b58 Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Tue, 8 Sep 2015 18:46:22 +0100 Subject: [PATCH 09/10] add scenario overrides --- src/localisation/LanguagePack.cpp | 106 ++++++++++++++++++++++++++++-- src/localisation/LanguagePack.h | 19 +++++- src/localisation/language.cpp | 11 ++++ src/localisation/language.h | 2 + src/scenario.c | 81 ++++++++++++++++------- 5 files changed, 192 insertions(+), 27 deletions(-) diff --git a/src/localisation/LanguagePack.cpp b/src/localisation/LanguagePack.cpp index e2a9ca60ad..cacc01da88 100644 --- a/src/localisation/LanguagePack.cpp +++ b/src/localisation/LanguagePack.cpp @@ -13,6 +13,9 @@ extern "C" { constexpr rct_string_id ObjectOverrideBase = 0x6000; constexpr int ObjectOverrideMaxStringCount = 4; +constexpr rct_string_id ScenarioOverrideBase = 0x7000; +constexpr int ScenarioOverrideMaxStringCount = 3; + LanguagePack *LanguagePack::FromFile(int id, const utf8 *path) { assert(path != NULL); @@ -54,6 +57,7 @@ LanguagePack::LanguagePack(int id, const utf8 *text) _stringData = NULL; _currentGroup = NULL; _currentObjectOverride = NULL; + _currentScenarioOverride = NULL; auto reader = UTF8StringReader(text); while (reader.CanRead()) { @@ -74,6 +78,14 @@ LanguagePack::LanguagePack(int id, const utf8 *text) } } } + for (size_t i = 0; i < _scenarioOverrides.size(); i++) { + for (int j = 0; j < ScenarioOverrideMaxStringCount; j++) { + const utf8 **strPtr = &(_scenarioOverrides[i].strings[j]); + if (*strPtr != NULL) { + *strPtr = (utf8*)(stringDataBaseAddress + (size_t)*strPtr); + } + } + } // Destruct the string builder to free memory _stringDataSB = StringBuilder(); @@ -86,7 +98,17 @@ LanguagePack::~LanguagePack() } const utf8 *LanguagePack::GetString(int stringId) const { - if (stringId >= ObjectOverrideBase) { + if (stringId >= ScenarioOverrideBase) { + int offset = stringId - ScenarioOverrideBase; + int ooIndex = offset / ScenarioOverrideMaxStringCount; + int ooStringIndex = offset % ScenarioOverrideMaxStringCount; + + if (_scenarioOverrides.size() > (size_t)ooIndex) { + return _scenarioOverrides[ooIndex].strings[ooStringIndex]; + } else { + return NULL; + } + }else if (stringId >= ObjectOverrideBase) { int offset = stringId - ObjectOverrideBase; int ooIndex = offset / ObjectOverrideMaxStringCount; int ooStringIndex = offset % ObjectOverrideMaxStringCount; @@ -124,6 +146,25 @@ rct_string_id LanguagePack::GetObjectOverrideStringId(const char *objectIdentifi return STR_NONE; } +rct_string_id LanguagePack::GetScenarioOverrideStringId(const utf8 *scenarioFilename, int index) +{ + assert(scenarioFilename != NULL); + assert(index < ScenarioOverrideMaxStringCount); + + int ooIndex = 0; + for (const ScenarioOverride &scenarioOverride : _scenarioOverrides) { + if (_stricmp(scenarioOverride.filename, scenarioFilename) == 0) { + if (scenarioOverride.strings[index] == NULL) { + return STR_NONE; + } + return ScenarioOverrideBase + (ooIndex * ScenarioOverrideMaxStringCount) + index; + } + ooIndex++; + } + + return STR_NONE; +} + LanguagePack::ObjectOverride *LanguagePack::GetObjectOverride(const char *objectIdentifier) { assert(objectIdentifier != NULL); @@ -137,6 +178,19 @@ LanguagePack::ObjectOverride *LanguagePack::GetObjectOverride(const char *object return false; } +LanguagePack::ScenarioOverride *LanguagePack::GetScenarioOverride(const utf8 *scenarioIdentifier) +{ + assert(scenarioIdentifier != NULL); + + for (size_t i = 0; i < _scenarioOverrides.size(); i++) { + ScenarioOverride *so = &_scenarioOverrides[i]; + if (_stricmp(so->name, scenarioIdentifier) == 0) { + return so; + } + } + return false; +} + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Parsing //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -209,7 +263,10 @@ void LanguagePack::ParseLine(IStringReader *reader) SkipToEndOfLine(reader); break; case '[': - ParseGroup(reader); + ParseGroupObject(reader); + break; + case '<': + ParseGroupScenario(reader); break; case '\r': case '\n': @@ -223,7 +280,7 @@ void LanguagePack::ParseLine(IStringReader *reader) } } -void LanguagePack::ParseGroup(IStringReader *reader) +void LanguagePack::ParseGroupObject(IStringReader *reader) { auto sb = StringBuilder(); int codepoint; @@ -253,6 +310,7 @@ void LanguagePack::ParseGroup(IStringReader *reader) if (sb.GetLength() == 8) { _currentGroup = sb.GetString(); _currentObjectOverride = GetObjectOverride(_currentGroup); + _currentScenarioOverride = NULL; if (_currentObjectOverride == NULL) { _objectOverrides.push_back(ObjectOverride()); _currentObjectOverride = &_objectOverrides[_objectOverrides.size() - 1]; @@ -263,6 +321,42 @@ void LanguagePack::ParseGroup(IStringReader *reader) } } +void LanguagePack::ParseGroupScenario(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->TryPeek(&codepoint)) { + if (IsNewLine(codepoint)) break; + + reader->Skip(); + if (codepoint == '>') { + closedCorrectly = true; + break; + } + sb.Append(codepoint); + } + + if (closedCorrectly) { + SafeFree(_currentGroup); + + _currentGroup = sb.GetString(); + _currentObjectOverride = NULL; + _currentScenarioOverride = GetScenarioOverride(_currentGroup); + if (_currentScenarioOverride == NULL) { + _scenarioOverrides.push_back(ScenarioOverride()); + _currentScenarioOverride = &_scenarioOverrides[_scenarioOverrides.size() - 1]; + memset(_currentScenarioOverride, 0, sizeof(ObjectOverride)); + _currentScenarioOverride->filename = sb.GetString(); + } + } +} + void LanguagePack::ParseString(IStringReader *reader) { auto sb = StringBuilder(); @@ -344,7 +438,11 @@ void LanguagePack::ParseString(IStringReader *reader) _strings[stringId] = relativeOffset; } else { - _currentObjectOverride->strings[stringId] = relativeOffset; + if (_currentObjectOverride != NULL) { + _currentObjectOverride->strings[stringId] = relativeOffset; + } else { + _currentScenarioOverride->strings[stringId] = relativeOffset; + } } _stringDataSB.Append(sb.GetBuffer()); diff --git a/src/localisation/LanguagePack.h b/src/localisation/LanguagePack.h index ba09b387a5..56a3479184 100644 --- a/src/localisation/LanguagePack.h +++ b/src/localisation/LanguagePack.h @@ -30,6 +30,7 @@ public: } rct_string_id GetObjectOverrideStringId(const char *objectIdentifier, int index); + rct_string_id GetScenarioOverrideStringId(const utf8 *scenarioFilename, int index); private: struct ObjectOverride { @@ -37,13 +38,27 @@ private: const utf8 *strings[4]; }; + struct ScenarioOverride { + const utf8 *filename; + union { + const utf8 *strings[3]; + struct { + const utf8 *name; + const utf8 *park; + const utf8 *details; + }; + }; + }; + int _id; utf8 *_stringData; std::vector _strings; std::vector _objectOverrides; + std::vector _scenarioOverrides; LanguagePack(int id, const utf8 *text); ObjectOverride *GetObjectOverride(const char *objectIdentifier); + ScenarioOverride *GetScenarioOverride(const utf8 *scenarioFilename); /////////////////////////////////////////////////////////////////////////// // Parsing @@ -51,9 +66,11 @@ private: StringBuilder _stringDataSB; utf8 *_currentGroup; ObjectOverride *_currentObjectOverride; + ScenarioOverride *_currentScenarioOverride; void ParseLine(IStringReader *reader); - void ParseGroup(IStringReader *reader); + void ParseGroupObject(IStringReader *reader); + void ParseGroupScenario(IStringReader *reader); void ParseString(IStringReader *reader); bool ParseToken(IStringReader *reader, uint32 *token); diff --git a/src/localisation/language.cpp b/src/localisation/language.cpp index 1f7904072f..5d3323408a 100644 --- a/src/localisation/language.cpp +++ b/src/localisation/language.cpp @@ -385,4 +385,15 @@ rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/ } } +bool language_get_localised_scenario_strings(const utf8 *scenarioFilename, rct_string_id *outStringIds) +{ + outStringIds[0] = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename, 0); + outStringIds[1] = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename, 1); + outStringIds[2] = _languageCurrent->GetScenarioOverrideStringId(scenarioFilename, 2); + return + outStringIds[0] != (rct_string_id)STR_NONE || + outStringIds[1] != (rct_string_id)STR_NONE || + outStringIds[2] != (rct_string_id)STR_NONE; +} + } diff --git a/src/localisation/language.h b/src/localisation/language.h index 66b7928306..4cc301146e 100644 --- a/src/localisation/language.h +++ b/src/localisation/language.h @@ -82,4 +82,6 @@ int utf8_length(const utf8 *text); wchar_t *utf8_to_widechar(const utf8 *src); utf8 *widechar_to_utf8(const wchar_t *src); +bool language_get_localised_scenario_strings(const utf8 *scenarioFilename, rct_string_id *outStringIds); + #endif diff --git a/src/scenario.c b/src/scenario.c index 0a8ddebea0..d8260bae5f 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -73,14 +73,29 @@ int scenario_load_basic(const char *path, rct_s6_header *header, rct_s6_info *in SDL_RWclose(rw); RCT2_GLOBAL(0x009AA00C, uint8) = 0; - // Checks for a scenario string object (possibly for localisation) - if ((info->entry.flags & 0xFF) != 255) { - if (object_get_scenario_text(&info->entry)) { - rct_stex_entry* stex_entry = RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TEXT_TEMP_CHUNK, rct_stex_entry*); - format_string(info->name, stex_entry->scenario_name, NULL); - format_string(info->details, stex_entry->details, NULL); - RCT2_GLOBAL(0x009AA00C, uint8) = stex_entry->var_06; - object_free_scenario_text(); + // Get filename + utf8 filename[MAX_PATH]; + strcpy(filename, path_get_filename(path)); + path_remove_extension(filename); + + rct_string_id localisedStringIds[3]; + if (language_get_localised_scenario_strings(filename, localisedStringIds)) { + if (localisedStringIds[0] != (rct_string_id)STR_NONE) { + strncpy(info->name, language_get_string(localisedStringIds[0]), 64); + } + if (localisedStringIds[2] != (rct_string_id)STR_NONE) { + strncpy(info->details, language_get_string(localisedStringIds[2]), 256); + } + } else { + // Checks for a scenario string object (possibly for localisation) + if ((info->entry.flags & 0xFF) != 255) { + if (object_get_scenario_text(&info->entry)) { + rct_stex_entry* stex_entry = RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TEXT_TEMP_CHUNK, rct_stex_entry*); + format_string(info->name, stex_entry->scenario_name, NULL); + format_string(info->details, stex_entry->details, NULL); + RCT2_GLOBAL(0x009AA00C, uint8) = stex_entry->var_06; + object_free_scenario_text(); + } } } return 1; @@ -282,23 +297,45 @@ void scenario_begin() strcpy((char*)RCT2_ADDRESS_SCENARIO_DETAILS, s6Info->details); strcpy((char*)RCT2_ADDRESS_SCENARIO_NAME, s6Info->name); - rct_stex_entry* stex = g_stexEntries[0]; - if ((int)stex != -1) { - char *buffer = (char*)RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER; + { + // Get filename + utf8 filename[MAX_PATH]; + strcpy(filename, _scenarioFileName); + path_remove_extension(filename); - // Set localised park name - format_string(buffer, stex->park_name, 0); - park_set_name(buffer); + rct_string_id localisedStringIds[3]; + if (language_get_localised_scenario_strings(filename, localisedStringIds)) { + if (localisedStringIds[0] != (rct_string_id)STR_NONE) { + strncpy((char*)RCT2_ADDRESS_SCENARIO_NAME, language_get_string(localisedStringIds[0]), 31); + ((char*)RCT2_ADDRESS_SCENARIO_NAME)[31] = '\0'; + } + if (localisedStringIds[1] != (rct_string_id)STR_NONE) { + park_set_name(language_get_string(localisedStringIds[1])); + } + if (localisedStringIds[2] != (rct_string_id)STR_NONE) { + strncpy((char*)RCT2_ADDRESS_SCENARIO_DETAILS, language_get_string(localisedStringIds[2]), 255); + ((char*)RCT2_ADDRESS_SCENARIO_DETAILS)[255] = '\0'; + } + } else { + rct_stex_entry* stex = g_stexEntries[0]; + if ((int)stex != -1) { + char *buffer = (char*)RCT2_ADDRESS_COMMON_STRING_FORMAT_BUFFER; - // Set localised scenario name - format_string(buffer, stex->scenario_name, 0); - strncpy((char*)RCT2_ADDRESS_SCENARIO_NAME, buffer, 31); - ((char*)RCT2_ADDRESS_SCENARIO_NAME)[31] = '\0'; + // Set localised park name + format_string(buffer, stex->park_name, 0); + park_set_name(buffer); - // Set localised scenario details - format_string(buffer, stex->details, 0); - strncpy((char*)RCT2_ADDRESS_SCENARIO_DETAILS, buffer, 255); - ((char*)RCT2_ADDRESS_SCENARIO_DETAILS)[255] = '\0'; + // Set localised scenario name + format_string(buffer, stex->scenario_name, 0); + strncpy((char*)RCT2_ADDRESS_SCENARIO_NAME, buffer, 31); + ((char*)RCT2_ADDRESS_SCENARIO_NAME)[31] = '\0'; + + // Set localised scenario details + format_string(buffer, stex->details, 0); + strncpy((char*)RCT2_ADDRESS_SCENARIO_DETAILS, buffer, 255); + ((char*)RCT2_ADDRESS_SCENARIO_DETAILS)[255] = '\0'; + } + } } // Set the last saved game path From cc6832cf488f35d7c7d8b2778f40b16050840a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Janiszewski?= Date: Wed, 9 Sep 2015 00:08:28 +0100 Subject: [PATCH 10/10] fix travis builds by forcing it to ubuntu 14.04 This (for now) will force Travis' hand to use ubuntu 14.04 which has mingw 4.8 available. It is enough to compile the project as it stands. --- .travis.yml | 3 +++ install.sh | 1 + 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 863be8d7cd..116b0b1198 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,3 +17,6 @@ cache: apt: true sudo: required + +services: + - docker diff --git a/install.sh b/install.sh index e834c27d66..93d64307f5 100755 --- a/install.sh +++ b/install.sh @@ -74,6 +74,7 @@ if [[ `uname` == "Darwin" ]]; then popd fi elif [[ `uname` == "Linux" ]]; then + sudo apt-get update if [[ -z "$TRAVIS" ]]; then sudo apt-get install -y binutils-mingw-w64-i686 gcc-mingw-w64-i686 g++-mingw-w64-i686 cmake if [[ -z "$DISABLE_G2_BUILD" ]]; then