1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2025-12-24 00:03:11 +01:00

add basic support for localisation object string overriding

This commit is contained in:
IntelOrca
2015-09-04 23:27:47 +01:00
parent 7a0478404e
commit 8085abadd3
11 changed files with 384 additions and 260 deletions

View File

@@ -64,7 +64,7 @@
<ClCompile Include="..\src\localisation\convert.c" />
<ClCompile Include="..\src\localisation\currency.c" />
<ClCompile Include="..\src\localisation\date.c" />
<ClCompile Include="..\src\localisation\language.c" />
<ClCompile Include="..\src\localisation\language.cpp" />
<ClCompile Include="..\src\localisation\LanguagePack.cpp" />
<ClCompile Include="..\src\localisation\localisation.c" />
<ClCompile Include="..\src\localisation\real_names.c" />

View File

@@ -216,9 +216,6 @@
<ClCompile Include="..\src\localisation\currency.c">
<Filter>Source\Localisation</Filter>
</ClCompile>
<ClCompile Include="..\src\localisation\language.c">
<Filter>Source\Localisation</Filter>
</ClCompile>
<ClCompile Include="..\src\ride\ride.c">
<Filter>Source\Ride</Filter>
</ClCompile>
@@ -540,6 +537,9 @@
<ClCompile Include="..\src\localisation\LanguagePack.cpp">
<Filter>Source\Localisation</Filter>
</ClCompile>
<ClCompile Include="..\src\localisation\language.cpp">
<Filter>Source\Localisation</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\src\management\award.h">

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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;
};

View File

@@ -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
///////////////////////////////////////////////////////////////////////////

View File

@@ -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<utf8>(_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<utf8>(_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;

View File

@@ -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;

View File

@@ -10,6 +10,9 @@ extern "C" {
#include "LanguagePack.h"
#include <SDL.h>
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;
}

View File

@@ -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<utf8*> _strings;
std::vector<const utf8*> _strings;
std::vector<ObjectOverride> _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);
};

View File

@@ -18,6 +18,10 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*****************************************************************************/
#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;
}
}
}