diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 195ef95..a0643dc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -141,6 +141,7 @@ target_sources(system3 PRIVATE fileio.cpp resource.cpp utfsjis.cpp + encoding.cpp texthook.cpp fm/makofm.cpp fm/mako_fmgen.cpp diff --git a/src/common.h b/src/common.h index bf067b1..87f48fa 100644 --- a/src/common.h +++ b/src/common.h @@ -48,14 +48,6 @@ inline void strcpy_s(char* dst, size_t n, const char* src) #endif // !WIN32 -static inline bool is_1byte_message(uint8_t c) { - return c == ' ' || (0xa1 <= c && c <= 0xdd); -} - -static inline bool is_2byte_message(uint8_t c) { - return (0x81 <= c && c <= 0x9f) || 0xe0 <= c; -} - // resource.cpp SDL_RWops* open_resource(const char* name, const char* type); SDL_RWops* open_file(const char* name); diff --git a/src/encoding.cpp b/src/encoding.cpp new file mode 100644 index 0000000..d6e48e0 --- /dev/null +++ b/src/encoding.cpp @@ -0,0 +1,38 @@ +#include "encoding.h" +#include "utfsjis.h" + +class SjisEncoding : public Encoding { +public: + int mblen(const unsigned char* s) + { + return is_2byte(*s) ? 2 : 1; + } + + int next_codepoint(const unsigned char** s) + { + int code = *(*s)++; + if (is_2byte(code)) + code = (code << 8) | (uint8)*(*s)++; + return sjis_to_unicode(code); + } + +private: + bool is_2byte(unsigned char c) { + return (0x81 <= c && c <= 0x9f) || 0xe0 <= c; + } +}; + +int Encoding::mbslen(const unsigned char* s) +{ + int len = 0; + while (*s) { + s += mblen(s); + len++; + } + return len; +} + +std::unique_ptr Encoding::create() +{ + return std::make_unique(); +} diff --git a/src/encoding.h b/src/encoding.h new file mode 100644 index 0000000..a164ba7 --- /dev/null +++ b/src/encoding.h @@ -0,0 +1,30 @@ +#ifndef _ENCODING_H_ +#define _ENCODING_H_ + +#include + +class Encoding { + public: + static std::unique_ptr create(); + + virtual ~Encoding() = default; + + // Returns Unicode codepoint of the first character of *s, and advances *s + // to the next character. + virtual int next_codepoint(const unsigned char** s) = 0; + int next_codepoint(const char** s) { + return next_codepoint(reinterpret_cast(s)); + } + // Returns byte length of the first character of s. + virtual int mblen(const unsigned char* s) = 0; + int mblen(const char* s) { + return mblen(reinterpret_cast(s)); + } + // Returns the number of characters in s. + int mbslen(const unsigned char* s); + int mbslen(const char* s) { + return mbslen(reinterpret_cast(s)); + } +}; + +#endif // _ENCODING_H_ diff --git a/src/sys/ags_text.cpp b/src/sys/ags_text.cpp index 681d3ea..26774e3 100644 --- a/src/sys/ags_text.cpp +++ b/src/sys/ags_text.cpp @@ -6,6 +6,7 @@ #include #include "ags.h" +#include "encoding.h" #include "utfsjis.h" #include "texthook.h" @@ -347,7 +348,6 @@ int convert_to_hankaku(int code) void AGS::draw_text(const char* string, bool text_wait) { - int p = 0; int screen, dest_x, dest_y, font_size; uint8 font_color; @@ -384,15 +384,8 @@ void AGS::draw_text(const char* string, bool text_wait) // Adjust dest_y if the font height is larger than the specified size. dest_y -= (ascent - descent - font_size) / 2; - while(string[p] != '\0') { - // 文字コード取得 - uint8 c = (uint8)string[p++]; - uint16 code = c; - if(is_2byte_message(c)) { - code = (code << 8) | (uint8)string[p++]; - } - code = sjis_to_unicode(code); - + while (*string) { + int code = nact->encoding->next_codepoint(&string); if(draw_hankaku) { code = convert_to_hankaku(code); } else { @@ -421,7 +414,7 @@ void AGS::draw_text(const char* string, bool text_wait) dest_x += advance; } - if (!draw_menu && text_wait && c != ' ') { + if (!draw_menu && text_wait && code != ' ') { // 画面更新 if(screen == 0) draw_screen(text_dest_x, dest_y, dest_x - text_dest_x, ascent - descent); diff --git a/src/sys/nact.cpp b/src/sys/nact.cpp index bf6283f..d229fd9 100644 --- a/src/sys/nact.cpp +++ b/src/sys/nact.cpp @@ -6,6 +6,7 @@ #include #include "nact.h" +#include "encoding.h" #include "ags.h" #include "mako.h" #include "msgskip.h" @@ -19,7 +20,11 @@ extern SDL_Window* g_window; // 初期化 NACT::NACT(int sys_ver, uint32 crc32_a, uint32 crc32_b, const Config& config) - : sys_ver(sys_ver), crc32_a(crc32_a), crc32_b(crc32_b), lang(get_language()) + : sys_ver(sys_ver), + crc32_a(crc32_a), + crc32_b(crc32_b), + lang(get_language()), + encoding(Encoding::create()) { platform_initialize(); @@ -287,15 +292,13 @@ void NACT::execute() message(cmd); break; default: - if(is_1byte_message(cmd) || is_2byte_message(cmd)) { + if (is_message(cmd)) { ungetd(); message(0); + } else if (cmd >= 0x20 && cmd < 0x7f) { + fatal("Unknown Command: '%c' at page = %d, addr = %d", cmd, scenario_page, prev_addr); } else { - if(cmd >= 0x20 && cmd < 0x7f) { - fatal("Unknown Command: '%c' at page = %d, addr = %d", cmd, scenario_page, prev_addr); - } else { - fatal("Unknown Command: %02x at page = %d, addr = %d", cmd, scenario_page, prev_addr); - } + fatal("Unknown Command: %02x at page = %d, addr = %d", cmd, scenario_page, prev_addr); } break; } @@ -304,10 +307,9 @@ void NACT::execute() void NACT::skip_string(uint8 terminator) { for (uint8 c = getd(); c != terminator; c = getd()) { - if (c == '\\') - c = getd(); - if (is_2byte_message(c)) - getd(); + if (c != '\\') + ungetd(); + scenario_addr += encoding->mblen(scenario_data + scenario_addr); } } @@ -315,24 +317,18 @@ void NACT::get_string(char* buf, int size, uint8 terminator) { int start_addr = scenario_addr; - uint8 c; int i = 0; - while ((c = getd()) != terminator) { - if (i + 1 >= size) - goto err; - if (c == '\\') - c = getd(); - buf[i++] = c; - if (is_2byte_message(c)) - buf[i++] = getd(); + for (uint8 c = getd(); c != terminator; c = getd()) { + if (c != '\\') + ungetd(); + int len = encoding->mblen(scenario_data + scenario_addr); + if (i + len >= size) + fatal("String buffer overrun. page = %d, addr = %d", scenario_page, start_addr); + memcpy(&buf[i], &scenario_data[scenario_addr], len); + i += len; + scenario_addr += len; } - if (i >= size) - goto err; buf[i] = '\0'; - return; - - err: - fatal("String buffer overrun. page = %d, addr = %d", scenario_page, start_addr); } void NACT::message(uint8 terminator) @@ -342,16 +338,10 @@ void NACT::message(uint8 terminator) get_string(buf, sizeof(buf), terminator); } else { uint8* begin = &scenario_data[scenario_addr]; - uint8* end = begin; - for (;;) { - if (is_1byte_message(*end)) - end++; - else if (is_2byte_message(*end)) - end += 2; - else - break; - } - int len = end - begin; + uint8* p = begin; + while (is_message(*p)) + p += encoding->mblen(p); + int len = p - begin; scenario_addr += len; strncpy(buf, reinterpret_cast(begin), len); diff --git a/src/sys/nact.h b/src/sys/nact.h index abc19eb..ebe1b03 100644 --- a/src/sys/nact.h +++ b/src/sys/nact.h @@ -7,6 +7,7 @@ #ifndef _NACT_H_ #define _NACT_H_ +#include #include #include #include "../common.h" @@ -82,6 +83,7 @@ class AGS; class MAKO; class MsgSkip; +class Encoding; struct Config; class NACT @@ -211,6 +213,7 @@ protected: return val; } void ungetd() { scenario_addr--; } + bool is_message(uint8_t c) { return c == ' ' || c & 0x80; } void skip_string(uint8 terminator); void get_string(char* buf, int size, uint8 terminator); @@ -263,6 +266,7 @@ public: uint32 crc32_a; // ADISK uint32 crc32_b; // BDISK for D.P.S -SG- and Super D.P.S Language lang; + std::unique_ptr encoding; // for Y27 char tvar[10][33]; diff --git a/src/sys/nact_sys1.cpp b/src/sys/nact_sys1.cpp index 9a9c385..e1258f2 100644 --- a/src/sys/nact_sys1.cpp +++ b/src/sys/nact_sys1.cpp @@ -17,6 +17,7 @@ #include "msgskip.h" #include "crc32.h" #include "../fileio.h" +#include "encoding.h" #include "texthook.h" // メニューの改ページ @@ -202,20 +203,15 @@ void NACT_Sys1::cmd_branch() } else if(cmd == 'Z') { cali(); cali(); - } else if(is_1byte_message(cmd)) { - // message (1 byte) - } else if(is_2byte_message(cmd)) { - // message (2 bytes) - getd(); } else if (cmd == '\'' || cmd == '"') { // SysEng skip_string(cmd); + } else if (is_message(cmd)) { + ungetd(); + scenario_addr += encoding->mblen(scenario_data + scenario_addr); + } else if (cmd >= 0x20 && cmd < 0x7f) { + fatal("Unknown Command: '%c' at page = %d, addr = %d", cmd, scenario_page, prev_addr); } else { - if(cmd >= 0x20 && cmd < 0x7f) { - fatal("Unknown Command: '%c' at page = %d, addr = %d", cmd, scenario_page, prev_addr); - } else { - fatal("Unknown Command: %02x at page = %d, addr = %d", cmd, scenario_page, prev_addr); - } - break; + fatal("Unknown Command: %02x at page = %d, addr = %d", cmd, scenario_page, prev_addr); } } } diff --git a/src/sys/nact_sys2.cpp b/src/sys/nact_sys2.cpp index 5fc8bcd..3decff1 100644 --- a/src/sys/nact_sys2.cpp +++ b/src/sys/nact_sys2.cpp @@ -12,6 +12,7 @@ #include "msgskip.h" #include "crc32.h" #include "../fileio.h" +#include "encoding.h" #include "texthook.h" #define WAIT(tm) \ @@ -175,11 +176,8 @@ void NACT_Sys2::cmd_branch() if (val == '\'' || val == '"') { // SysEng skip_string(val); } else { - while (val != ':') { - if(is_2byte_message(val)) - getd(); + while (val != ':') val = getd(); - } } } else if(cmd == 'N') { cali(); @@ -256,20 +254,15 @@ void NACT_Sys2::cmd_branch() } else if(cmd == 'Z') { cali(); cali(); - } else if(is_1byte_message(cmd)) { - // message (1 byte) - } else if(is_2byte_message(cmd)) { - // message (2 bytes) - getd(); } else if (cmd == '\'' || cmd == '"') { // SysEng skip_string(cmd); + } else if (is_message(cmd)) { + ungetd(); + scenario_addr += encoding->mblen(scenario_data + scenario_addr); + } else if (cmd >= 0x20 && cmd < 0x7f) { + fatal("Unknown Command: '%c' at page = %d, addr = %d", cmd, scenario_page, prev_addr); } else { - if(cmd >= 0x20 && cmd < 0x7f) { - fatal("Unknown Command: '%c' at page = %d, addr = %d", cmd, scenario_page, prev_addr); - } else { fatal("Unknown Command: %02x at page = %d, addr = %d", cmd, scenario_page, prev_addr); - } - break; } } } @@ -857,12 +850,7 @@ void NACT_Sys2::cmd_m() } else { int p = 0; while(d != ':') { - if(is_2byte_message(d)) { - string[p++] = d; - string[p++] = getd(); - } else { - string[p++] = d; - } + string[p++] = d; d = getd(); } string[p] = '\0'; @@ -1234,23 +1222,13 @@ void NACT_Sys2::cmd_y() case 228: case 229: if(1 <= param && param <= 10) { - char string[22]; - int len = cmd - 220, p = 0, q = 0; - uint8 d; - while((d = (uint8)tvar[param - 1][p]) != '\0') { - if(is_2byte_message(d)) { - string[p] = tvar[param - 1][p]; - p++; - } - string[p] = tvar[param - 1][p]; - p++; - q++; + ags->draw_text(tvar[param - 1]); + int padlen = cmd - 220 - encoding->mbslen(tvar[param - 1]); + if (padlen > 0) { + char pad[10] = " "; + pad[padlen] = '\0'; + ags->draw_text(pad); } - for(int i = q; i < len; i++) { - string[p++] = 0x20; - } - string[p] = '\0'; - ags->draw_text(string); } break; case 252: diff --git a/src/sys/nact_sys3.cpp b/src/sys/nact_sys3.cpp index f1fd37d..92a11e0 100644 --- a/src/sys/nact_sys3.cpp +++ b/src/sys/nact_sys3.cpp @@ -12,6 +12,7 @@ #include "msgskip.h" #include "crc32.h" #include "../fileio.h" +#include "encoding.h" #include "texthook.h" NACT_Sys3::NACT_Sys3(uint32 crc32_a, uint32 crc32_b, const Config& config) @@ -715,12 +716,7 @@ void NACT_Sys3::cmd_m() } else { int p = 0; while(d != ':') { - if(is_2byte_message(d)) { - string[p++] = d; - string[p++] = getd(); - } else { - string[p++] = d; - } + string[p++] = d; d = getd(); } string[p] = '\0'; @@ -1296,21 +1292,13 @@ void NACT_Sys3::cmd_y() case 228: case 229: { - char string[21]; - int s = 0, d = 0; - for(int i = 0; i < cmd - 220; i++) { - if(tvar[param - 1][s] == '\0') { - string[d++] = 0x20; - } else { - uint8 code = tvar[param - 1][s++]; - string[d++] = code; - if(is_2byte_message(code)) { - string[d++] = tvar[param - 1][s++]; - } - } + ags->draw_text(tvar[param - 1]); + int padlen = cmd - 220 - encoding->mbslen(tvar[param - 1]); + if (padlen > 0) { + char pad[10] = " "; + pad[padlen] = '\0'; + ags->draw_text(pad); } - string[d] = '\0'; - ags->draw_text(string); } break; case 230: diff --git a/src/win/nact_win.cpp b/src/win/nact_win.cpp index d7f3978..d6663e8 100644 --- a/src/win/nact_win.cpp +++ b/src/win/nact_win.cpp @@ -63,8 +63,8 @@ INT_PTR CALLBACK TextDialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lPara static NACT* nact; char string[64]; wchar_t wstring[64]; - int pnt, cnt; - + int len; + switch(msg) { case WM_CLOSE: EndDialog(hDlg, IDCANCEL); @@ -75,6 +75,7 @@ INT_PTR CALLBACK TextDialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lPara // init dialog swprintf_s(wstring, 64, L"文字列を入力してください(最大%d文字)", nact->tvar_maxlen); SetWindowTextW(GetDlgItem(hDlg, IDC_TEXT), wstring); + // FIXME: encoding conversion Edit_SetText(GetDlgItem(hDlg, IDC_EDITBOX), nact->tvar[nact->tvar_index - 1]); if(nact->tvar[nact->tvar_index - 1][0] == '\0') { EnableWindow(GetDlgItem(hDlg, IDOK), FALSE); @@ -86,17 +87,8 @@ INT_PTR CALLBACK TextDialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lPara case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_EDITBOX: - GetDlgItemText(hDlg, IDC_EDITBOX, string, 32); - pnt = cnt = 0; - while(string[pnt] != '\0') { - unsigned char dat = string[pnt]; - if(is_2byte_message(dat)) { - pnt++; - } - pnt++; - cnt++; - } - if(cnt == 0 || cnt > nact->tvar_maxlen) { + len = GetWindowTextLengthW(GetDlgItem(hDlg, IDC_EDITBOX)); + if (len == 0 || len > nact->tvar_maxlen) { EnableWindow(GetDlgItem(hDlg, IDOK), FALSE); } else { EnableWindow(GetDlgItem(hDlg, IDOK), TRUE); @@ -104,6 +96,7 @@ INT_PTR CALLBACK TextDialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lPara break; case IDOK: GetDlgItemText(hDlg, IDC_EDITBOX, string, 32); + // FIXME: encoding conversion strcpy_s(nact->tvar[nact->tvar_index - 1], 22, string); EndDialog(hDlg, IDOK); break;