From 69e01dab7c5299ecc015e530738c9404e0852195 Mon Sep 17 00:00:00 2001 From: Akash Mozumdar Date: Tue, 30 Oct 2018 20:50:50 -0400 Subject: [PATCH] allow different codepages --- GUI/host/host.cc | 15 +++++---- GUI/host/textthread.cc | 15 ++++----- GUI/host/textthread.h | 6 ++-- GUI/misc.cpp | 62 ++++++++++++++++++++++++++------------ GUI/misc.h | 6 ++-- GUI/tests.cpp | 2 +- include/types.h | 1 + vnrhook/hijack/texthook.cc | 5 ++- 8 files changed, 69 insertions(+), 43 deletions(-) diff --git a/GUI/host/host.cc b/GUI/host/host.cc index d012d5b..7b4cb35 100644 --- a/GUI/host/host.cc +++ b/GUI/host/host.cc @@ -31,14 +31,13 @@ namespace void DispatchText(ThreadParam tp, const BYTE* text, int len) { - if (!text || len <= 0) return; LOCK(hostMutex); - TextThread *it; - if ((it = textThreadsByParams[tp]) == nullptr) - if (textThreadsByParams.size() < MAX_THREAD_COUNT) - OnCreate(it = textThreadsByParams[tp] = new TextThread(tp, Host::GetHookParam(tp).type)); - else return Host::AddConsoleOutput(L"too many text threads: stopping"); - it->AddText(text, len); + if (textThreadsByParams[tp] == nullptr) + { + if (textThreadsByParams.size() > MAX_THREAD_COUNT) return Host::AddConsoleOutput(L"too many text threads: can't create more"); + OnCreate(textThreadsByParams[tp] = new TextThread(tp)); + } + textThreadsByParams[tp]->AddText(text, len); } void RemoveThreads(std::function removeIf) @@ -144,7 +143,7 @@ namespace Host void Start(ProcessEventCallback onAttach, ProcessEventCallback onDetach, ThreadEventCallback onCreate, ThreadEventCallback onRemove, TextThread::OutputCallback output) { OnAttach = onAttach; OnDetach = onDetach; OnCreate = onCreate; OnRemove = onRemove; TextThread::Output = output; - OnCreate(textThreadsByParams[CONSOLE] = new TextThread(CONSOLE, USING_UNICODE)); + OnCreate(textThreadsByParams[CONSOLE] = new TextThread(CONSOLE)); StartPipe(); } diff --git a/GUI/host/textthread.cc b/GUI/host/textthread.cc index c29599a..895d99d 100644 --- a/GUI/host/textthread.cc +++ b/GUI/host/textthread.cc @@ -8,7 +8,7 @@ #include #include -TextThread::TextThread(ThreadParam tp, DWORD status) : handle(threadCounter++), name(Host::GetHookName(tp.pid, tp.hook)), tp(tp), status(status) {} +TextThread::TextThread(ThreadParam tp) : handle(threadCounter++), name(Host::GetHookName(tp.pid, tp.hook)), tp(tp), hp(Host::GetHookParam(tp)) {} TextThread::~TextThread() { @@ -19,7 +19,7 @@ TextThread::~TextThread() std::wstring TextThread::GetStorage() { - LOCK(ttMutex); + LOCK(threadMutex); return storage; } @@ -27,7 +27,7 @@ void TextThread::Flush() { std::wstring sentence; { - LOCK(ttMutex); + LOCK(threadMutex); if (buffer.empty()) return; if (buffer.size() < maxBufferSize && GetTickCount() - timestamp < flushDelay) return; sentence = buffer; @@ -46,17 +46,18 @@ void TextThread::AddSentence(std::wstring sentence) // Dispatch to extensions occurs here. Don't hold mutex! Extensions might take a while! if (Output(this, sentence)) { - LOCK(ttMutex); + LOCK(threadMutex); storage += sentence; } } void TextThread::AddText(const BYTE* data, int len) { - LOCK(ttMutex); - buffer += status & USING_UNICODE + if (len < 0) return; + LOCK(threadMutex); + buffer += hp.type & USING_UNICODE ? std::wstring((wchar_t*)data, len / 2) - : StringToWideString(std::string((char*)data, len), status & USING_UTF8 ? CP_UTF8 : SHIFT_JIS); + : StringToWideString(std::string((char*)data, len), hp.codepage); if (std::all_of(buffer.begin(), buffer.end(), [&](wchar_t c) { return repeatingChars.count(c) > 0; })) buffer.clear(); timestamp = GetTickCount(); } diff --git a/GUI/host/textthread.h b/GUI/host/textthread.h index 1edfb43..389f468 100644 --- a/GUI/host/textthread.h +++ b/GUI/host/textthread.h @@ -19,7 +19,7 @@ public: inline static int maxBufferSize = 200; inline static int threadCounter = 0; - TextThread(ThreadParam tp, DWORD status); + TextThread(ThreadParam tp); ~TextThread(); std::wstring GetStorage(); @@ -29,6 +29,7 @@ public: const int64_t handle; const std::wstring name; const ThreadParam tp; + const HookParam hp; private: void Flush(); @@ -36,8 +37,7 @@ private: std::wstring buffer; std::wstring storage; std::unordered_set repeatingChars; - std::recursive_mutex ttMutex; - DWORD status; + std::recursive_mutex threadMutex; HANDLE deletionEvent = CreateEventW(nullptr, FALSE, FALSE, NULL); std::thread flushThread = std::thread([&] { while (WaitForSingleObject(deletionEvent, 10) == WAIT_TIMEOUT) Flush(); }); diff --git a/GUI/misc.cpp b/GUI/misc.cpp index 20e3325..059f19d 100644 --- a/GUI/misc.cpp +++ b/GUI/misc.cpp @@ -53,6 +53,14 @@ namespace } RCode.remove(0, 1); + // [codepage#] + QRegularExpressionMatch codepage = QRegularExpression("^([0-9]+)#").match(RCode); + if (codepage.hasMatch()) + { + hp.codepage = codepage.captured(1).toInt(); + RCode.remove(0, codepage.captured(0).length()); + } + // [*deref_offset|0] if (RCode.at(0).unicode() == L'0') RCode.remove(0, 1); // Legacy QRegularExpressionMatch deref = QRegularExpression("^\\*(\\-?[[:xdigit:]]+)").match(RCode); @@ -109,6 +117,14 @@ namespace HCode.remove(0, 1); } + // [codepage#] + QRegularExpressionMatch codepage = QRegularExpression("^([0-9]+)#").match(HCode); + if (codepage.hasMatch()) + { + hp.codepage = codepage.captured(1).toInt(); + HCode.remove(0, codepage.captured(0).length()); + } + // data_offset QRegularExpressionMatch dataOffset = QRegularExpression("^\\-?[[:xdigit:]]+").match(HCode); if (!dataOffset.hasMatch()) return {}; @@ -164,12 +180,31 @@ namespace return hp; } + QString GenerateRCode(HookParam hp) + { + QString RCode = "/R"; + QTextStream codeBuilder(&RCode); + + if (hp.type & USING_UNICODE) codeBuilder << "Q"; + else if (hp.type & USING_UTF8) codeBuilder << "V"; + else codeBuilder << "S"; + + if (hp.codepage != SHIFT_JIS && hp.codepage != CP_UTF8) codeBuilder << hp.codepage << "#"; + + codeBuilder.setIntegerBase(16); + codeBuilder.setNumberFlags(QTextStream::UppercaseDigits); + + if (hp.type & DATA_INDIRECT) codeBuilder << "*" << hp.index; + + codeBuilder << "@" << hp.address; + + return RCode; + } + QString GenerateHCode(HookParam hp, DWORD processId) { QString HCode = "/H"; QTextStream codeBuilder(&HCode); - codeBuilder.setIntegerBase(16); - codeBuilder.setNumberFlags(QTextStream::UppercaseDigits); if (hp.type & USING_UNICODE) { @@ -185,6 +220,11 @@ namespace } if (hp.type & NO_CONTEXT) codeBuilder << "N"; + if (hp.codepage != SHIFT_JIS && hp.codepage != CP_UTF8) codeBuilder << hp.codepage << "#"; + + codeBuilder.setIntegerBase(16); + codeBuilder.setNumberFlags(QTextStream::UppercaseDigits); + if (hp.offset < 0) hp.offset += 4; if (hp.split < 0) hp.split += 4; @@ -214,24 +254,6 @@ namespace return HCode; } - - QString GenerateRCode(HookParam hp) - { - QString RCode = "/R"; - QTextStream codeBuilder(&RCode); - codeBuilder.setIntegerBase(16); - codeBuilder.setNumberFlags(QTextStream::UppercaseDigits); - - if (hp.type & USING_UNICODE) codeBuilder << "Q"; - else if (hp.type & USING_UTF8) codeBuilder << "V"; - else codeBuilder << "S"; - - if (hp.type & DATA_INDIRECT) codeBuilder << "*" << hp.index; - - codeBuilder << "@" << hp.address; - - return RCode; - } } std::optional ParseCode(QString code) diff --git a/GUI/misc.h b/GUI/misc.h index 7c701a8..7cab830 100644 --- a/GUI/misc.h +++ b/GUI/misc.h @@ -13,11 +13,11 @@ QString GenerateCode(HookParam hp, DWORD processId); static QString CodeInfoDump = "Enter hook code\r\n\ -/H{A|B|W|S|Q|V}[N]data_offset[*deref_offset1][:split_offset[*deref_offset2]]@addr[:module[:func]]\r\n\ +/H{A|B|W|S|Q|V}[N][codepage#]data_offset[*deref_offset1][:split_offset[*deref_offset2]]@addr[:module[:func]]\r\n\ OR\r\n\ Enter read code\r\n\ -/R{S|Q|V}[*deref_offset|0]@addr\r\n\ -All numbers in hexadecimal\r\n\ +/R{S|Q|V}[#codepage][*deref_offset|0]@addr\r\n\ +All numbers except codepage in hexadecimal\r\n\ A/B: Shift-JIS char little/big endian\r\n\ W: UTF-16 char\r\n\ S/Q/V: Shift-JIS/UTF-16/UTF-8 string\r\n\ diff --git a/GUI/tests.cpp b/GUI/tests.cpp index 96be56d..a831b8a 100644 --- a/GUI/tests.cpp +++ b/GUI/tests.cpp @@ -3,7 +3,7 @@ static int TESTS = [] { - assert(ParseCode("/HQ-c*C:C*1C@4AA:gdi.dll:GetTextOutA")); + assert(ParseCode("/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")); assert(ParseCode("/HB4@0")); assert(ParseCode("/RS*10@44")); assert(!ParseCode("HQ@4")); diff --git a/include/types.h b/include/types.h index 98d6349..7dd1958 100644 --- a/include/types.h +++ b/include/types.h @@ -20,6 +20,7 @@ struct HookParam wchar_t module[MAX_MODULE_SIZE]; char function[MAX_MODULE_SIZE]; DWORD type; // flags + UINT codepage; // text encoding WORD length_offset; // index of the string length DWORD user_value; // 7/20/2014: jichi additional parameters for PSP games diff --git a/vnrhook/hijack/texthook.cc b/vnrhook/hijack/texthook.cc index 4bdd36f..7000332 100644 --- a/vnrhook/hijack/texthook.cc +++ b/vnrhook/hijack/texthook.cc @@ -232,13 +232,16 @@ bool TextHook::InsertHookCode() bool TextHook::UnsafeInsertHookCode() { - if (hp.type & MODULE_OFFSET) // Map hook offset to real address. + if (hp.type & MODULE_OFFSET) // Map hook offset to real address if (hp.type & FUNCTION_OFFSET) if (FARPROC function = GetProcAddress(GetModuleHandleW(hp.module), hp.function)) hp.insertion_address += (uint64_t)function; else return ConsoleOutput("Textractor: UnsafeInsertHookCode: FAILED: function not present"), false; else if (HMODULE moduleBase = GetModuleHandleW(hp.module)) hp.insertion_address += (uint64_t)moduleBase; else return ConsoleOutput("Textractor: UnsafeInsertHookCode: FAILED: module not present"), false; + if (hp.type & USING_UTF8) hp.codepage = CP_UTF8; + if (hp.codepage == 0) hp.codepage = SHIFT_JIS; // Use Shift-JIS unless custom encoding was specified + BYTE* original; insert: if (MH_STATUS err = MH_CreateHook((void*)hp.insertion_address, (void*)trampoline, (void**)&original))