From 14d0033db350e34da4bfc2c0ba14a2278df18681 Mon Sep 17 00:00:00 2001 From: kichikuou Date: Sat, 4 Sep 2021 15:21:52 +0900 Subject: [PATCH] Add support for UTF-8 encoded scenario files If `encoding = utf8` is specified in system3.ini, system3-sdl2 uses UTF-8 as its multibyte character encoding. It will be used to run games translated into non-(Japanese|English) languages. --- src/common.h | 4 +++ src/config.cpp | 8 +++--- src/config.h | 1 + src/encoding.cpp | 57 +++++++++++++++++++++++++++++++++++++++++- src/encoding.h | 2 +- src/sys/nact.cpp | 2 +- src/sys/nact.h | 1 + src/sys/nact_crc32.cpp | 10 ++++++++ 8 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/common.h b/src/common.h index 87f48fa..3b662c1 100644 --- a/src/common.h +++ b/src/common.h @@ -48,6 +48,10 @@ inline void strcpy_s(char* dst, size_t n, const char* src) #endif // !WIN32 +#ifdef _MSC_VER +#define strcasecmp _stricmp +#endif + // resource.cpp SDL_RWops* open_resource(const char* name, const char* type); SDL_RWops* open_file(const char* name); diff --git a/src/config.cpp b/src/config.cpp index d4731eb..1bd8e7a 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -9,10 +9,6 @@ #include #endif -#ifdef _MSC_VER -#define strcasecmp _stricmp -#endif - #define INIFILENAME "system3.ini" namespace { @@ -64,6 +60,8 @@ Config::Config(int argc, char *argv[]) use_fm = true; else if (strcmp(argv[i], "-game") == 0) game_id = argv[++i]; + else if (strcmp(argv[i], "-encoding") == 0) + encoding = argv[++i]; } } @@ -103,6 +101,8 @@ void Config::load_ini() use_fm = to_bool(val, lineno); else if (sscanf(line, "game = %s", val)) game_id = val; + else if (sscanf(line, "encoding = %s", val)) + encoding = val; else if (!is_empty_line(line)) WARNING(INIFILENAME ":%d parse error", lineno); } else if (!is_empty_line(line)) { diff --git a/src/config.h b/src/config.h index d1d9a5c..b64e032 100644 --- a/src/config.h +++ b/src/config.h @@ -7,6 +7,7 @@ struct Config { public: std::string font_file; std::string game_id; + std::string encoding; std::string save_dir; std::string playlist; bool use_fm = false; diff --git a/src/encoding.cpp b/src/encoding.cpp index d6e48e0..7bbf890 100644 --- a/src/encoding.cpp +++ b/src/encoding.cpp @@ -22,6 +22,50 @@ private: } }; +class Utf8Encoding : public Encoding { +public: + int mblen(const unsigned char* s) + { + if (*s <= 0xbf) + return 1; + if (*s <= 0xdf) + return 2; + if (*s <= 0xef) + return 3; + return 4; + } + + int next_codepoint(const unsigned char** str) + { + int code; + const unsigned char *s = *str; + + if (*s <= 0x7f) { + code = *s++; + } else if (*s <= 0xbf) { + // Invalid UTF-8 sequence + code = '?'; + s++; + } else if (*s <= 0xdf) { + code = (s[0] & 0x1f) << 6 | (s[1] & 0x3f); + s += 2; + } else if (*s <= 0xef) { + code = (s[0] & 0xf) << 12 | (s[1] & 0x3f) << 6 | (s[2] & 0x3f); + s += 3; + } else if (*s <= 0xf7) { + code = (s[0] & 0x7) << 18 | (s[1] & 0x3f) << 12 | (s[2] & 0x3f) << 6 | (s[3] & 0x3f); + s += 4; + } else { + code = 0xfffd; // REPLACEMENT CHARACTER + s++; + while (0x80 <= *s && *s <= 0xbf) + s++; + } + *str = s; + return code; + } +}; + int Encoding::mbslen(const unsigned char* s) { int len = 0; @@ -32,7 +76,18 @@ int Encoding::mbslen(const unsigned char* s) return len; } -std::unique_ptr Encoding::create() +std::unique_ptr Encoding::create(const char* name) { + if (!strcasecmp(name, "Shift_JIS") || + !strcasecmp(name, "Shift-JIS") || + !strcasecmp(name, "SJIS") || + !strcasecmp(name, "CP932")) + return std::make_unique(); + + if (!strcasecmp(name, "UTF-8") || + !strcasecmp(name, "UTF8")) + return std::make_unique(); + + WARNING("Unrecognized encoding: \"%s\"", name); return std::make_unique(); } diff --git a/src/encoding.h b/src/encoding.h index a164ba7..4e1ae05 100644 --- a/src/encoding.h +++ b/src/encoding.h @@ -5,7 +5,7 @@ class Encoding { public: - static std::unique_ptr create(); + static std::unique_ptr create(const char* name); virtual ~Encoding() = default; diff --git a/src/sys/nact.cpp b/src/sys/nact.cpp index d229fd9..2806fb9 100644 --- a/src/sys/nact.cpp +++ b/src/sys/nact.cpp @@ -24,7 +24,7 @@ NACT::NACT(int sys_ver, uint32 crc32_a, uint32 crc32_b, const Config& config) crc32_a(crc32_a), crc32_b(crc32_b), lang(get_language()), - encoding(Encoding::create()) + encoding(Encoding::create(get_encoding_name(config))) { platform_initialize(); diff --git a/src/sys/nact.h b/src/sys/nact.h index ebe1b03..bbd4e6a 100644 --- a/src/sys/nact.h +++ b/src/sys/nact.h @@ -258,6 +258,7 @@ public: static const int get_sys_ver(uint32 crc32_a, uint32 crc32_b); const char* get_title(); Language get_language(); + const char* get_encoding_name(const Config& config); void text_wait(); void set_skip_menu_state(bool enabled, bool checked); diff --git a/src/sys/nact_crc32.cpp b/src/sys/nact_crc32.cpp index 0d54662..3ce5154 100644 --- a/src/sys/nact_crc32.cpp +++ b/src/sys/nact_crc32.cpp @@ -8,6 +8,7 @@ #include #include "crc32.h" #include "../fileio.h" +#include "config.h" namespace { @@ -156,3 +157,12 @@ Language NACT::get_language() return entry->language; return JAPANESE; } + +const char* NACT::get_encoding_name(const Config& config) +{ + if (!config.encoding.empty()) + return config.encoding.c_str(); + + // TODO: Determine encoding from crc32 + return "Shift_JIS"; +}