From 1e7a59f9cdbe90ecf94a23772817be0a6f9fd34a Mon Sep 17 00:00:00 2001 From: kichikuou Date: Wed, 4 Dec 2024 15:43:41 +0900 Subject: [PATCH] Add texthook option `-texthook print` prints text to console, and `-texthook copy` automatically copies text to clipboard (for use with text hooker tools). On Windows, copy mode can be toggled from the menu bar. --- README.md | 7 ++ src/config.cpp | 19 +++++ src/config.h | 2 + src/sdlmain.cpp | 2 + src/texthook.cpp | 172 ++++++++++++++++++++++++++++++++----------- src/texthook.h | 7 ++ src/win/nact_win.cpp | 18 ++++- src/win/resource.h | 1 + src/win/resource.rc | 3 + system3.ini.example | 6 ++ 10 files changed, 191 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 935a200..93b007b 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,13 @@ tone generator emulation. Specifies the MIDI device number to use. If not specified, the first available device is used. +#### `-texthook` _mode_ +Specifies the text hook mode. This option is used for capturing in-game text +for translation or other purposes. The available modes are: +- `none`: Disable text hooking. +- `print`: Print the captured text to the console. +- `copy`: Copy the captured text to the clipboard. + #### `-game` _game_id_ As System1-3 have slight variations depending on the game, `system3` uses the fingerprint of the scenario file (ADISK.DAT) to identify the game being played. diff --git a/src/config.cpp b/src/config.cpp index a938a20..66ef025 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -77,6 +77,21 @@ std::pair parse_keyval(char *line) return { trim(line), trim(eq + 1) }; } +TexthookMode parse_texthook_mode(const char *s, int lineno = -1) +{ + if (!strcasecmp(s, "none")) + return TEXTHOOK_NONE; + if (!strcasecmp(s, "print")) + return TEXTHOOK_PRINT; + if (!strcasecmp(s, "copy")) + return TEXTHOOK_COPY; + if (lineno == -1) + ERROR("Command line: Invalid texthook mode '%s'", s); + else + ERROR(INIFILENAME ":%d Invalid texthook mode '%s'", lineno, s); + return TEXTHOOK_NONE; +} + void init_string(std::string& s, Encoding* encoding, const char *dflt) { char *encoded = encoding->fromUtf8(s.empty() ? dflt : s.c_str()); @@ -120,6 +135,8 @@ Config::Config(int argc, char *argv[]) title = argv[++i]; else if (strcmp(argv[i], "-scanline") == 0) scanline = true; + else if (strcmp(argv[i], "-texthook") == 0) + texthook_mode = parse_texthook_mode(argv[++i]); } } @@ -178,6 +195,8 @@ void Config::load_ini() title = val; else if (!strcasecmp(key, "scanline")) scanline = to_bool(val, lineno); + else if (!strcasecmp(key, "texthook")) + texthook_mode = parse_texthook_mode(val, lineno); else WARNING(INIFILENAME ":%d unknown key '%s'", lineno, key); } else if (current_section == STRING) { diff --git a/src/config.h b/src/config.h index 6f4851e..a88b6f6 100644 --- a/src/config.h +++ b/src/config.h @@ -2,6 +2,7 @@ #define CONFIG_H_ #include +#include "texthook.h" class Encoding; @@ -32,6 +33,7 @@ struct Config { bool use_fm = false; bool no_antialias = false; bool scanline = false; + TexthookMode texthook_mode = TEXTHOOK_NONE; private: void load_ini(); diff --git a/src/sdlmain.cpp b/src/sdlmain.cpp index 4fe863d..11f61eb 100644 --- a/src/sdlmain.cpp +++ b/src/sdlmain.cpp @@ -97,6 +97,8 @@ int main(int argc, char *argv[]) EM_ASM( xsystem35.shell.windowSizeChanged(); ); #endif + texthook_set_mode(config.texthook_mode); + int exit_code = NACT_RESTART; while (exit_code == NACT_RESTART) { exit_code = g_nact->mainloop(); diff --git a/src/texthook.cpp b/src/texthook.cpp index e446f0f..1f52807 100644 --- a/src/texthook.cpp +++ b/src/texthook.cpp @@ -1,9 +1,15 @@ +#include #include +#include #include "texthook.h" -#ifdef __EMSCRIPTEN__ // ---------------------------------------------- +#ifdef __EMSCRIPTEN__ #include +void texthook_set_mode(TexthookMode m) { + // Do nothing +} + void texthook_character(int page, int character) { EM_ASM_({ xsystem35.texthook.message(String.fromCharCode($0), $1); }, character, page); @@ -21,59 +27,139 @@ EM_JS(void, texthook_keywait, (void), { xsystem35.texthook.keywait(); }); -#elif defined(TEXTHOOK_PRINT) // -------------------------------------- +#else -static int newlines = 0; +namespace { + +class TextHookHandler { +public: + virtual void character(int page, int c) = 0; + virtual void newline(void) = 0; + virtual void nextpage(void) = 0; + virtual void keywait(void) = 0; +}; + +class None : public TextHookHandler { +public: + void character(int page, int c) override {} + void newline(void) override {} + void nextpage(void) override {} + void keywait(void) override {} +} none; + +class Print : public TextHookHandler { +public: + void character(int page, int c) override { + if (newlines) + printf("%d:", page); + newlines = 0; + + if (c <= 0x7f) { + putchar(c); + } else if (c <= 0x7ff) { + putchar(0xc0 | c >> 6); + putchar(0x80 | (c & 0x3f)); + } else { + putchar(0xe0 | c >> 12); + putchar(0x80 | (c >> 6 & 0x3f)); + putchar(0x80 | (c & 0x3f)); + } + } + + void newline(void) override { + if (newlines < 2) { + putchar('\n'); + fflush(stdout); + newlines++; + } + } + + void nextpage(void) override { + while (newlines < 2) { + putchar('\n'); + fflush(stdout); + newlines++; + } + } + + void keywait(void) override { + newline(); + } + +private: + int newlines = 0; +} print; + +class Copy : public TextHookHandler { +public: + void character(int page, int c) override { + if (c <= 0x7f) { + buf += c; + } else if (c <= 0x7ff) { + buf += static_cast(0xc0 | c >> 6); + buf += static_cast(0x80 | (c & 0x3f)); + } else { + buf += static_cast(0xe0 | c >> 12); + buf += static_cast(0x80 | (c >> 6 & 0x3f)); + buf += static_cast(0x80 | (c & 0x3f)); + } + } + + void newline(void) override { + buf += "\n"; + } + + void nextpage(void) override { + if (!buf.empty()) + copy_to_clipboard(); + } + + void keywait(void) override { + if (!buf.empty()) + copy_to_clipboard(); + } + +private: + std::string buf; + + void copy_to_clipboard(void) { + SDL_SetClipboardText(buf.c_str()); + buf.clear(); + } +} copy; + +TextHookHandler *handler = &none; + +} // namespace + +void texthook_set_mode(TexthookMode mode) { + switch (mode) { + case TEXTHOOK_NONE: + handler = &none; + break; + case TEXTHOOK_PRINT: + handler = &print; + break; + case TEXTHOOK_COPY: + handler = © + break; + } +} void texthook_character(int page, int c) { - if (newlines) - printf("%d:", page); - newlines = 0; - - if (c <= 0x7f) { - putchar(c); - } else if (c <= 0x7ff) { - putchar(0xc0 | c >> 6); - putchar(0x80 | (c & 0x3f)); - } else { - putchar(0xe0 | c >> 12); - putchar(0x80 | (c >> 6 & 0x3f)); - putchar(0x80 | (c & 0x3f)); - } + handler->character(page, c); } void texthook_newline(void) { - if (newlines < 2) { - putchar('\n'); - fflush(stdout); - newlines++; - } + handler->newline(); } void texthook_nextpage(void) { - while (newlines < 2) { - putchar('\n'); - fflush(stdout); - newlines++; - } + handler->nextpage(); } void texthook_keywait(void) { - texthook_newline(); + handler->keywait(); } -#else // -------------------------------------------------------------- - -void texthook_character(int page, int c) { -} - -void texthook_newline(void) { -} - -void texthook_nextpage(void) { -} - -void texthook_keywait(void) { -} - -#endif // ------------------------------------------------------------- +#endif diff --git a/src/texthook.h b/src/texthook.h index aba722f..5f4be6c 100644 --- a/src/texthook.h +++ b/src/texthook.h @@ -3,6 +3,13 @@ extern "C" { +enum TexthookMode { + TEXTHOOK_NONE, + TEXTHOOK_PRINT, + TEXTHOOK_COPY, +}; + +void texthook_set_mode(TexthookMode mode); void texthook_character(int page, int character); void texthook_newline(void); void texthook_nextpage(void); diff --git a/src/win/nact_win.cpp b/src/win/nact_win.cpp index 14e5aa7..e623ce0 100644 --- a/src/win/nact_win.cpp +++ b/src/win/nact_win.cpp @@ -8,12 +8,15 @@ #include "ags.h" #include "mako.h" #include "msgskip.h" +#include "texthook.h" #include "resource.h" extern SDL_Window* g_window; namespace { +bool auto_copy_enabled = false; + HWND get_hwnd(SDL_Window* window) { SDL_SysWMinfo info; SDL_VERSION(&info.version); @@ -21,15 +24,18 @@ HWND get_hwnd(SDL_Window* window) { return info.info.win.window; } -void init_menu(bool mouse_move_enabled, bool scanline_enabled) +void init_menu(bool mouse_move_enabled, const Config& config) { HINSTANCE hinst = (HINSTANCE)GetModuleHandle(NULL); HMENU hmenu = LoadMenu(hinst, MAKEINTRESOURCE(IDR_MENU1)); SetMenu(get_hwnd(g_window), hmenu); if (mouse_move_enabled) CheckMenuItem(hmenu, ID_OPTION_MOUSE_MOVE, MF_BYCOMMAND | MFS_CHECKED); - if (scanline_enabled) + if (config.scanline) CheckMenuItem(hmenu, ID_SCANLINE, MF_BYCOMMAND | MFS_CHECKED); + auto_copy_enabled = config.texthook_mode == TEXTHOOK_COPY; + if (auto_copy_enabled) + CheckMenuItem(hmenu, ID_TEXT_AUTO_COPY, MF_BYCOMMAND | MFS_CHECKED); } void init_console(int sys_ver) @@ -131,7 +137,7 @@ void NACT::text_dialog() void NACT::platform_initialize() { - init_menu(mouse_move_enabled, config.scanline); + init_menu(mouse_move_enabled, config); init_console(sys_ver); SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); } @@ -217,6 +223,12 @@ bool NACT::handle_platform_event(const SDL_Event& e) CheckMenuItem(GetMenu(get_hwnd(g_window)), ID_TEXT_WAIT, MF_BYCOMMAND | text_wait_enb ? MFS_CHECKED : MFS_UNCHECKED); break; + case ID_TEXT_AUTO_COPY: + auto_copy_enabled = !auto_copy_enabled; + CheckMenuItem(GetMenu(get_hwnd(g_window)), ID_TEXT_AUTO_COPY, MF_BYCOMMAND | + auto_copy_enabled ? MFS_CHECKED : MFS_UNCHECKED); + texthook_set_mode(auto_copy_enabled ? TEXTHOOK_COPY : TEXTHOOK_NONE); + break; } break; case MM_MCINOTIFY: diff --git a/src/win/resource.h b/src/win/resource.h index 4a663f3..c89ea4a 100644 --- a/src/win/resource.h +++ b/src/win/resource.h @@ -31,5 +31,6 @@ #define ID_OPTION_CD 40023 #define ID_TEXT_SKIP 40030 #define ID_TEXT_WAIT 40031 +#define ID_TEXT_AUTO_COPY 40032 #endif // _RESOURCE_H_ diff --git a/src/win/resource.rc b/src/win/resource.rc index 02aa779..626bb36 100644 --- a/src/win/resource.rc +++ b/src/win/resource.rc @@ -112,6 +112,7 @@ BEGIN BEGIN MENUITEM "Skip", ID_TEXT_SKIP MENUITEM "Wait", ID_TEXT_WAIT + MENUITEM "Auto Copy to Clipboard", ID_TEXT_AUTO_COPY END END @@ -170,6 +171,7 @@ BEGIN BEGIN MENUITEM "スキップ", ID_TEXT_SKIP MENUITEM "ウェイトあり", ID_TEXT_WAIT + MENUITEM "クリップボードに自動コピー", ID_TEXT_AUTO_COPY END END @@ -228,6 +230,7 @@ BEGIN BEGIN MENUITEM "跳过", ID_TEXT_SKIP MENUITEM "等待", ID_TEXT_WAIT + MENUITEM "Auto Copy to Clipboard", ID_TEXT_AUTO_COPY END END diff --git a/system3.ini.example b/system3.ini.example index 6dfbf9e..f63189a 100644 --- a/system3.ini.example +++ b/system3.ini.example @@ -35,6 +35,12 @@ encoding = shift_jis ; original Japanese game title will be used. title = Rance 4.1 ~Save the Medicine Plant!~ +; Text hook mode. +; none : No text hook (default) +; print : Print text to console +; copy : Automatically copy text to clipboard (for use with text hooker tools) +texthook = none + ; The [string] section allows you to override Japanese text embedded in the ; game engine. The values below are the original Japanese text. [string]