From 799cb53ec749ebefe2a937ef45c9e54b9a18c4ff Mon Sep 17 00:00:00 2001 From: IntelOrca Date: Wed, 29 Jul 2015 23:57:43 +0100 Subject: [PATCH] implement utf8, part 18 --- src/editor.c | 1 + src/game.c | 61 ++++++ src/game.h | 3 + src/localisation/convert.c | 318 ++++++++++++++++++++++++++++++++ src/localisation/localisation.h | 8 +- src/localisation/user.c | 6 +- src/management/news_item.c | 83 ++++----- src/management/news_item.h | 8 +- src/scenario.c | 2 + 9 files changed, 438 insertions(+), 52 deletions(-) diff --git a/src/editor.c b/src/editor.c index b3960732ce..43fe400d3e 100644 --- a/src/editor.c +++ b/src/editor.c @@ -499,6 +499,7 @@ static int editor_read_s6(const char *path) news_item_init_queue(); window_editor_main_open(); editor_finalise_main_view(); + game_convert_strings_to_utf8(); return 1; } diff --git a/src/game.c b/src/game.c index 3c0c753dae..db23454383 100644 --- a/src/game.c +++ b/src/game.c @@ -589,6 +589,66 @@ static void load_landscape() } } +/** + * Converts all the user strings and news item strings to UTF-8. + */ +void game_convert_strings_to_utf8() +{ + utf8 buffer[512]; + + // User strings + for (int i = 0; i < MAX_USER_STRINGS; i++) { + utf8 *userString = &gUserStrings[i * USER_STRING_MAX_LENGTH]; + + if (!str_is_null_or_empty(userString)) { + rct2_to_utf8(buffer, userString); + memcpy(userString, buffer, 31); + userString[31] = 0; + } + } + + // News items + for (int i = 0; i < MAX_NEWS_ITEMS; i++) { + rct_news_item *newsItem = news_item_get(i); + + if (!str_is_null_or_empty(newsItem->text)) { + rct2_to_utf8(buffer, newsItem->text); + memcpy(newsItem->text, buffer, 255); + newsItem->text[255] = 0; + } + } +} + +/** + * Converts all the user strings and news item strings to RCT2 encoding. + */ +void game_convert_strings_to_rct2(rct_s6_data *s6) +{ + char buffer[512]; + + // User strings + for (int i = 0; i < MAX_USER_STRINGS; i++) { + char *userString = &s6->custom_strings[i * USER_STRING_MAX_LENGTH]; + + if (!str_is_null_or_empty(userString)) { + utf8_to_rct2(buffer, userString); + memcpy(userString, buffer, 31); + userString[31] = 0; + } + } + + // News items + for (int i = 0; i < MAX_NEWS_ITEMS; i++) { + rct_news_item *newsItem = &s6->news_items[i]; + + if (!str_is_null_or_empty(newsItem->text)) { + utf8_to_rct2(buffer, newsItem->text); + memcpy(newsItem->text, buffer, 255); + newsItem->text[255] = 0; + } + } +} + /** * * rct2: 0x00675E1B @@ -650,6 +710,7 @@ int game_load_sv6(SDL_RWops* rw) map_update_tile_pointers(); reset_0x69EBE4(); openrct2_reset_object_tween_locations(); + game_convert_strings_to_utf8(); return 1; } diff --git a/src/game.h b/src/game.h index 55e48051e9..fa9dbf8120 100644 --- a/src/game.h +++ b/src/game.h @@ -23,6 +23,7 @@ #include "common.h" #include "platform/platform.h" +#include "scenario.h" enum GAME_COMMAND { GAME_COMMAND_SET_RIDE_APPEARANCE, @@ -117,5 +118,7 @@ int save_game(); void rct2_exit(); void rct2_exit_reason(rct_string_id title, rct_string_id body); void game_autosave(); +void game_convert_strings_to_utf8(); +void game_convert_strings_to_rct2(rct_s6_data *s6); #endif diff --git a/src/localisation/convert.c b/src/localisation/convert.c index fe77e039d9..fb4963bb82 100644 --- a/src/localisation/convert.c +++ b/src/localisation/convert.c @@ -7,6 +7,51 @@ typedef struct { static const encoding_convert_entry GB2312ToUnicodeTable[7445]; static const encoding_convert_entry Big5ToUnicodeTable[13710]; +static const encoding_convert_entry RCT2ToUnicodeTable[256]; + +int rct2_to_utf8(utf8 *dst, const char *src) +{ + int codepoint; + + utf8 *start = dst; + const char *ch = src; + while (*ch != 0) { + if (*ch == (char)0xFF) { + ch++; + + // Read wide char + uint8 a = *ch++; + uint8 b = *ch++; + codepoint = (a << 8) | b; + } else { + codepoint = (uint8)(*ch++); + codepoint = encoding_convert_rct2_to_unicode(codepoint); + } + + dst = utf8_write_codepoint(dst, codepoint); + } + dst = utf8_write_codepoint(dst, 0); + return dst - start; +} + +int utf8_to_rct2(char *dst, const utf8 *src) +{ + char *start = dst; + const utf8 *ch = src; + int codepoint; + while ((codepoint = utf8_get_next(ch, &ch)) != 0) { + codepoint = encoding_convert_unicode_to_rct2(codepoint); + if (codepoint < 256) { + *dst++ = (char)codepoint; + } else if (codepoint <= 0xFFFF) { + *dst++ = (char)0xFF; + *dst++ = (codepoint >> 8) & 0xFF; + *dst++ = codepoint & 0xFF; + } + } + *dst++ = 0; + return dst - start; +} static int encoding_search_compare(const void *pKey, const void *pEntry) { @@ -24,6 +69,20 @@ static wchar_t encoding_convert_x_to_unicode(wchar_t code, const encoding_conver else return entry->unicode; } +wchar_t encoding_convert_unicode_to_rct2(wchar_t unicode) +{ + // Can't do a binary search as its sorted by RCT2 code, not unicode + for (int i = 0; i < countof(RCT2ToUnicodeTable); i++) { + if (RCT2ToUnicodeTable[i].unicode == unicode) return RCT2ToUnicodeTable[i].code; + } + return unicode; +} + +wchar_t encoding_convert_rct2_to_unicode(wchar_t rct2str) +{ + return encoding_convert_x_to_unicode(rct2str, RCT2ToUnicodeTable, countof(RCT2ToUnicodeTable)); +} + wchar_t encoding_convert_gb2312_to_unicode(wchar_t gb2312) { return encoding_convert_x_to_unicode(gb2312 - 0x8080, GB2312ToUnicodeTable, countof(GB2312ToUnicodeTable)); @@ -34,6 +93,265 @@ wchar_t encoding_convert_big5_to_unicode(wchar_t big5) return encoding_convert_x_to_unicode(big5, Big5ToUnicodeTable, countof(Big5ToUnicodeTable)); } +static const encoding_convert_entry RCT2ToUnicodeTable[256] = { + { 0, 0 }, + { 1, FORMAT_MOVE_X }, + { 2, FORMAT_ADJUST_PALETTE }, + { 3, 3 }, + { 4, 4 }, + { 5, FORMAT_NEWLINE }, + { 6, FORMAT_NEWLINE_SMALLER }, + { 7, FORMAT_TINYFONT }, + { 8, FORMAT_BIGFONT }, + { 9, FORMAT_MEDIUMFONT }, + { 10, FORMAT_SMALLFONT }, + { 11, FORMAT_OUTLINE }, + { 12, FORMAT_OUTLINE_OFF }, + { 13, FORMAT_WINDOW_COLOUR_1 }, + { 14, FORMAT_WINDOW_COLOUR_2 }, + { 15, FORMAT_WINDOW_COLOUR_3 }, + { 16, 16 }, + { 17, FORMAT_NEWLINE_X_Y }, + { 18, 18 }, + { 19, 19 }, + { 20, 20 }, + { 21, 21 }, + { 22, 22 }, + { 23, FORMAT_INLINE_SPRITE }, + { 24, 24 }, + { 25, 25 }, + { 26, 26 }, + { 27, 27 }, + { 28, 28 }, + { 29, 29 }, + { 30, 30 }, + { 31, 31 }, + { 32, 32 }, + { 33, 33 }, + { 34, 34 }, + { 35, 35 }, + { 36, 36 }, + { 37, 37 }, + { 38, 38 }, + { 39, 39 }, + { 40, 40 }, + { 41, 41 }, + { 42, 42 }, + { 43, 43 }, + { 44, 44 }, + { 45, 45 }, + { 46, 46 }, + { 47, 47 }, + { 48, 48 }, + { 49, 49 }, + { 50, 50 }, + { 51, 51 }, + { 52, 52 }, + { 53, 53 }, + { 54, 54 }, + { 55, 55 }, + { 56, 56 }, + { 57, 57 }, + { 58, 58 }, + { 59, 59 }, + { 60, 60 }, + { 61, 61 }, + { 62, 62 }, + { 63, 63 }, + { 64, 64 }, + { 65, 65 }, + { 66, 66 }, + { 67, 67 }, + { 68, 68 }, + { 69, 69 }, + { 70, 70 }, + { 71, 71 }, + { 72, 72 }, + { 73, 73 }, + { 74, 74 }, + { 75, 75 }, + { 76, 76 }, + { 77, 77 }, + { 78, 78 }, + { 79, 79 }, + { 80, 80 }, + { 81, 81 }, + { 82, 82 }, + { 83, 83 }, + { 84, 84 }, + { 85, 85 }, + { 86, 86 }, + { 87, 87 }, + { 88, 88 }, + { 89, 89 }, + { 90, 90 }, + { 91, 91 }, + { 92, 92 }, + { 93, 93 }, + { 94, 94 }, + { 95, 95 }, + { 96, 96 }, + { 97, 97 }, + { 98, 98 }, + { 99, 99 }, + { 100, 100 }, + { 101, 101 }, + { 102, 102 }, + { 103, 103 }, + { 104, 104 }, + { 105, 105 }, + { 106, 106 }, + { 107, 107 }, + { 108, 108 }, + { 109, 109 }, + { 110, 110 }, + { 111, 111 }, + { 112, 112 }, + { 113, 113 }, + { 114, 114 }, + { 115, 115 }, + { 116, 116 }, + { 117, 117 }, + { 118, 118 }, + { 119, 119 }, + { 120, 120 }, + { 121, 121 }, + { 122, 122 }, + { 123, FORMAT_COMMA32 }, + { 124, FORMAT_INT32 }, + { 125, FORMAT_COMMA2DP32 }, + { 126, FORMAT_COMMA16 }, + { 127, FORMAT_UINT16 }, + { 128, FORMAT_CURRENCY2DP }, + { 129, FORMAT_CURRENCY }, + { 130, FORMAT_STRINGID }, + { 131, FORMAT_STRINGID2 }, + { 132, FORMAT_STRING }, + { 133, FORMAT_MONTHYEAR }, + { 134, FORMAT_MONTH }, + { 135, FORMAT_VELOCITY }, + { 136, FORMAT_POP16 }, + { 137, FORMAT_PUSH16 }, + { 138, FORMAT_DURATION }, + { 139, FORMAT_REALTIME }, + { 140, FORMAT_LENGTH }, + { 141, FORMAT_SPRITE }, + { 142, FORMAT_BLACK }, + { 143, FORMAT_GREY }, + { 144, FORMAT_WHITE }, + { 145, FORMAT_RED }, + { 146, FORMAT_GREEN }, + { 147, FORMAT_YELLOW }, + { 148, FORMAT_TOPAZ }, + { 149, FORMAT_CELADON }, + { 150, FORMAT_BABYBLUE }, + { 151, FORMAT_PALELAVENDER }, + { 152, FORMAT_PALEGOLD }, + { 153, FORMAT_LIGHTPINK }, + { 154, FORMAT_PEARLAQUA }, + { 155, FORMAT_PALESILVER }, + { 156, 156 }, + { 157, 157 }, + { 158, 158 }, + { 159, FORMAT_AMINUSCULE }, + { 160, FORMAT_UP }, + { 161, FORMAT_SYMBOL_i }, + { 162, 162 }, + { 163, FORMAT_POUND }, + { 164, 164 }, + { 165, FORMAT_YEN }, + { 166, 166 }, + { 167, 167 }, + { 168, 168 }, + { 169, FORMAT_COPYRIGHT }, + { 170, FORMAT_DOWN }, + { 171, FORMAT_LEFTGUILLEMET }, + { 172, FORMAT_TICK }, + { 173, FORMAT_CROSS }, + { 174, 174 }, + { 175, FORMAT_RIGHT }, + { 176, FORMAT_DEGREE }, + { 177, FORMAT_SYMBOL_RAILWAY }, + { 178, FORMAT_SQUARED }, + { 179, 179 }, + { 180, FORMAT_OPENQUOTES }, + { 181, FORMAT_EURO }, + { 182, FORMAT_SYMBOL_ROAD }, + { 183, FORMAT_SYMBOL_FLAG }, + { 184, FORMAT_APPROX }, + { 185, FORMAT_POWERNEGATIVEONE }, + { 186, FORMAT_BULLET }, + { 187, FORMAT_RIGHTGUILLEMET }, + { 188, FORMAT_SMALLUP }, + { 189, FORMAT_SMALLDOWN }, + { 190, FORMAT_LEFT }, + { 191, FORMAT_INVERTEDQUESTION }, + { 192, 192 }, + { 193, 193 }, + { 194, 194 }, + { 195, 195 }, + { 196, 196 }, + { 197, 197 }, + { 198, 198 }, + { 199, 199 }, + { 200, 200 }, + { 201, 201 }, + { 202, 202 }, + { 203, 203 }, + { 204, 204 }, + { 205, 205 }, + { 206, 206 }, + { 207, 207 }, + { 208, 208 }, + { 209, 209 }, + { 210, 210 }, + { 211, 211 }, + { 212, 212 }, + { 213, 213 }, + { 214, 214 }, + { 215, 215 }, + { 216, 216 }, + { 217, 217 }, + { 218, 218 }, + { 219, 219 }, + { 220, 220 }, + { 221, 221 }, + { 222, 222 }, + { 223, 223 }, + { 224, 224 }, + { 225, 225 }, + { 226, 226 }, + { 227, 227 }, + { 228, 228 }, + { 229, 229 }, + { 230, 230 }, + { 231, 231 }, + { 232, 232 }, + { 233, 233 }, + { 234, 234 }, + { 235, 235 }, + { 236, 236 }, + { 237, 237 }, + { 238, 238 }, + { 239, 239 }, + { 240, 240 }, + { 241, 241 }, + { 242, 242 }, + { 243, 243 }, + { 244, 244 }, + { 245, 245 }, + { 246, 246 }, + { 247, 247 }, + { 248, 248 }, + { 249, 249 }, + { 250, 250 }, + { 251, 251 }, + { 252, 252 }, + { 253, 253 }, + { 254, 254 }, + { 255, 255 } +}; + static const encoding_convert_entry GB2312ToUnicodeTable[7445] = { { 8481, 12288 }, { 8482, 12289 }, diff --git a/src/localisation/localisation.h b/src/localisation/localisation.h index 6ffbfcfef8..51a9fe7b9c 100644 --- a/src/localisation/localisation.h +++ b/src/localisation/localisation.h @@ -41,13 +41,17 @@ size_t get_string_size(const utf8 *text); int get_string_length(const utf8 *text); void user_string_clear_all(); -rct_string_id user_string_allocate(int base, const char *text); +rct_string_id user_string_allocate(int base, const utf8 *text); void user_string_free(rct_string_id id); bool is_user_string_id(rct_string_id stringId); utf8 *win1252_to_utf8_alloc(const char *src); int win1252_to_utf8(utf8string dst, const char *src, int maxBufferLength); +int rct2_to_utf8(utf8 *dst, const char *src); +int utf8_to_rct2(char *dst, const utf8 *src); +wchar_t encoding_convert_rct2_to_unicode(wchar_t rct2str); +wchar_t encoding_convert_unicode_to_rct2(wchar_t unicode); wchar_t encoding_convert_gb2312_to_unicode(wchar_t gb2312); wchar_t encoding_convert_big5_to_unicode(wchar_t big5); @@ -58,4 +62,6 @@ wchar_t encoding_convert_big5_to_unicode(wchar_t big5); extern const char real_name_initials[16]; extern const char *real_names[1024]; +extern utf8 *gUserStrings; + #endif diff --git a/src/localisation/user.c b/src/localisation/user.c index 57358e4c65..e481edf0d1 100644 --- a/src/localisation/user.c +++ b/src/localisation/user.c @@ -21,7 +21,7 @@ #include "../addresses.h" #include "localisation.h" -char *gUserStrings = (char*)0x0135A8F4; +utf8 *gUserStrings = (char*)0x0135A8F4; static bool user_string_exists(const char *text); @@ -38,7 +38,7 @@ void user_string_clear_all() * * rct2: 0x006C421D */ -rct_string_id user_string_allocate(int base, const char *text) +rct_string_id user_string_allocate(int base, const utf8 *text) { int highBits = (base & 0x7F) << 9; bool allowDuplicates = base & 0x80; @@ -73,7 +73,7 @@ void user_string_free(rct_string_id id) gUserStrings[id * USER_STRING_MAX_LENGTH] = 0; } -static bool user_string_exists(const char *text) +static bool user_string_exists(const utf8 *text) { char *userString = gUserStrings; for (int i = 0; i < MAX_USER_STRINGS; i++, userString += USER_STRING_MAX_LENGTH) { diff --git a/src/management/news_item.c b/src/management/news_item.c index 74d0eeb8ae..7ab8e76c10 100644 --- a/src/management/news_item.c +++ b/src/management/news_item.c @@ -32,31 +32,27 @@ rct_news_item *gNewsItems = RCT2_ADDRESS(RCT2_ADDRESS_NEWS_ITEM_LIST, rct_news_i void window_game_bottom_toolbar_invalidate_news_item(); static int news_item_get_new_history_slot(); -#define MAX_NEWS 60 - -bool news_item_is_valid_idx(const uint8 idx) +bool news_item_is_valid_idx(int index) { - if (idx > MAX_NEWS) - { + if (index > MAX_NEWS_ITEMS) { log_error("Tried to get news item past MAX_NEWS."); return false; } return true; } -rct_news_item *news_item_get(const uint8 idx) +rct_news_item *news_item_get(int index) { - if (news_item_is_valid_idx(idx)) - { - return &gNewsItems[idx]; + if (news_item_is_valid_idx(index)) { + return &gNewsItems[index]; } else { return NULL; } } -bool news_item_is_empty(const uint8 idx) +bool news_item_is_empty(int index) { - return news_item_get(idx)->type == NEWS_ITEM_NULL; + return news_item_get(index)->type == NEWS_ITEM_NULL; } bool news_item_is_queue_empty() @@ -179,7 +175,7 @@ void news_item_close_current() newsItems[i] = newsItems[0]; // Set the end of the end of the history list - if (i < MAX_NEWS) + if (i < MAX_NEWS_ITEMS) newsItems[i + 1].type = NEWS_ITEM_NULL; // Invalidate the news window @@ -198,7 +194,7 @@ static void news_item_shift_history_up() { const int history_idx = 11; rct_news_item *history_start = news_item_get(history_idx); - const size_t count = sizeof(rct_news_item) * (MAX_NEWS - 1 - history_idx); + const size_t count = sizeof(rct_news_item) * (MAX_NEWS_ITEMS - 1 - history_idx); memmove(history_start, history_start + 1, count); } @@ -212,13 +208,13 @@ static int news_item_get_new_history_slot() int i; // Find an available history news item slot - for (i = 11; i <= MAX_NEWS; i++) + for (i = 11; i <= MAX_NEWS_ITEMS; i++) if (news_item_is_empty(i)) return i; // Dequeue the first history news item, shift history up news_item_shift_history_up(); - return MAX_NEWS; + return MAX_NEWS_ITEMS; } /** @@ -405,39 +401,36 @@ void news_item_open_subject(int type, int subject) } /** - * rct2: 0x0066E407 + * + * rct2: 0x0066E407 */ -void news_item_disable_news(uint8 type, uint32 assoc) { - // TODO: write test invalidating windows - int i; - for (i = 0; i < 11; i++) - { - if (!news_item_is_empty(i)) - { - rct_news_item * const newsItem = news_item_get(i); - if (type == newsItem->type && assoc == newsItem->assoc) { - newsItem->flags |= 0x1; - if (i == 0) { - window_game_bottom_toolbar_invalidate_news_item(); - } - } - } else { - break; +void news_item_disable_news(uint8 type, uint32 assoc) +{ + // TODO: write test invalidating windows + for (int i = 0; i < 11; i++) { + if (!news_item_is_empty(i)) { + rct_news_item * const newsItem = news_item_get(i); + if (type == newsItem->type && assoc == newsItem->assoc) { + newsItem->flags |= 0x1; + if (i == 0) { + window_game_bottom_toolbar_invalidate_news_item(); + } } - } + } else { + break; + } + } - for (i = 11; i <= MAX_NEWS; i++) - { - if (!news_item_is_empty(i)) - { - rct_news_item * const newsItem = news_item_get(i); - if (type == newsItem->type && assoc == newsItem->assoc) { - newsItem->flags |= 0x1; - window_invalidate_by_class(WC_RECENT_NEWS); - } - } else { - break; + for (int i = 11; i <= MAX_NEWS_ITEMS; i++) { + if (!news_item_is_empty(i)) { + rct_news_item * const newsItem = news_item_get(i); + if (type == newsItem->type && assoc == newsItem->assoc) { + newsItem->flags |= 0x1; + window_invalidate_by_class(WC_RECENT_NEWS); } - } + } else { + break; + } + } } diff --git a/src/management/news_item.h b/src/management/news_item.h index d4512aea9b..e1ed3b2f87 100644 --- a/src/management/news_item.h +++ b/src/management/news_item.h @@ -51,6 +51,8 @@ typedef struct { utf8 text[256]; // 0x0C } rct_news_item; +#define MAX_NEWS_ITEMS 60 + void news_item_init_queue(); void news_item_update_current(); void news_item_close_current(); @@ -59,9 +61,9 @@ void news_item_add_to_queue(uint8 type, rct_string_id string_id, uint32 assoc); void news_item_add_to_queue_raw(uint8 type, const char *text, uint32 assoc); void news_item_open_subject(int type, int subject); void news_item_disable_news(uint8 type, uint32 assoc); -rct_news_item *news_item_get(const uint8 idx); -bool news_item_is_empty(const uint8 idx); +rct_news_item *news_item_get(int index); +bool news_item_is_empty(int index); bool news_item_is_queue_empty(); -bool news_item_is_valid_idx(const uint8 idx); +bool news_item_is_valid_idx(int index); #endif diff --git a/src/scenario.c b/src/scenario.c index f1dd4f0e30..9d00389e92 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -180,6 +180,7 @@ int scenario_load(const char *path) map_update_tile_pointers(); reset_0x69EBE4(); openrct2_reset_object_tween_locations(); + game_convert_strings_to_utf8(); return 1; } @@ -965,6 +966,7 @@ int scenario_save(SDL_RWops* rw, int flags) memcpy(&s6->dword_010E63B8, (void*)0x010E63B8, 0x2E8570); scenario_fix_ghosts(s6); + game_convert_strings_to_rct2(s6); scenario_save_s6(rw, s6); free(s6);