allow different codepages

This commit is contained in:
Akash Mozumdar 2018-10-30 20:50:50 -04:00
parent dddbc00694
commit 69e01dab7c
8 changed files with 69 additions and 43 deletions

View File

@ -31,14 +31,13 @@ namespace
void DispatchText(ThreadParam tp, const BYTE* text, int len) void DispatchText(ThreadParam tp, const BYTE* text, int len)
{ {
if (!text || len <= 0) return;
LOCK(hostMutex); LOCK(hostMutex);
TextThread *it; if (textThreadsByParams[tp] == nullptr)
if ((it = textThreadsByParams[tp]) == nullptr) {
if (textThreadsByParams.size() < MAX_THREAD_COUNT) if (textThreadsByParams.size() > MAX_THREAD_COUNT) return Host::AddConsoleOutput(L"too many text threads: can't create more");
OnCreate(it = textThreadsByParams[tp] = new TextThread(tp, Host::GetHookParam(tp).type)); OnCreate(textThreadsByParams[tp] = new TextThread(tp));
else return Host::AddConsoleOutput(L"too many text threads: stopping"); }
it->AddText(text, len); textThreadsByParams[tp]->AddText(text, len);
} }
void RemoveThreads(std::function<bool(ThreadParam)> removeIf) void RemoveThreads(std::function<bool(ThreadParam)> removeIf)
@ -144,7 +143,7 @@ namespace Host
void Start(ProcessEventCallback onAttach, ProcessEventCallback onDetach, ThreadEventCallback onCreate, ThreadEventCallback onRemove, TextThread::OutputCallback output) void Start(ProcessEventCallback onAttach, ProcessEventCallback onDetach, ThreadEventCallback onCreate, ThreadEventCallback onRemove, TextThread::OutputCallback output)
{ {
OnAttach = onAttach; OnDetach = onDetach; OnCreate = onCreate; OnRemove = onRemove; TextThread::Output = 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(); StartPipe();
} }

View File

@ -8,7 +8,7 @@
#include <regex> #include <regex>
#include <algorithm> #include <algorithm>
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() TextThread::~TextThread()
{ {
@ -19,7 +19,7 @@ TextThread::~TextThread()
std::wstring TextThread::GetStorage() std::wstring TextThread::GetStorage()
{ {
LOCK(ttMutex); LOCK(threadMutex);
return storage; return storage;
} }
@ -27,7 +27,7 @@ void TextThread::Flush()
{ {
std::wstring sentence; std::wstring sentence;
{ {
LOCK(ttMutex); LOCK(threadMutex);
if (buffer.empty()) return; if (buffer.empty()) return;
if (buffer.size() < maxBufferSize && GetTickCount() - timestamp < flushDelay) return; if (buffer.size() < maxBufferSize && GetTickCount() - timestamp < flushDelay) return;
sentence = buffer; 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! // Dispatch to extensions occurs here. Don't hold mutex! Extensions might take a while!
if (Output(this, sentence)) if (Output(this, sentence))
{ {
LOCK(ttMutex); LOCK(threadMutex);
storage += sentence; storage += sentence;
} }
} }
void TextThread::AddText(const BYTE* data, int len) void TextThread::AddText(const BYTE* data, int len)
{ {
LOCK(ttMutex); if (len < 0) return;
buffer += status & USING_UNICODE LOCK(threadMutex);
buffer += hp.type & USING_UNICODE
? std::wstring((wchar_t*)data, len / 2) ? 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(); if (std::all_of(buffer.begin(), buffer.end(), [&](wchar_t c) { return repeatingChars.count(c) > 0; })) buffer.clear();
timestamp = GetTickCount(); timestamp = GetTickCount();
} }

View File

@ -19,7 +19,7 @@ public:
inline static int maxBufferSize = 200; inline static int maxBufferSize = 200;
inline static int threadCounter = 0; inline static int threadCounter = 0;
TextThread(ThreadParam tp, DWORD status); TextThread(ThreadParam tp);
~TextThread(); ~TextThread();
std::wstring GetStorage(); std::wstring GetStorage();
@ -29,6 +29,7 @@ public:
const int64_t handle; const int64_t handle;
const std::wstring name; const std::wstring name;
const ThreadParam tp; const ThreadParam tp;
const HookParam hp;
private: private:
void Flush(); void Flush();
@ -36,8 +37,7 @@ private:
std::wstring buffer; std::wstring buffer;
std::wstring storage; std::wstring storage;
std::unordered_set<wchar_t> repeatingChars; std::unordered_set<wchar_t> repeatingChars;
std::recursive_mutex ttMutex; std::recursive_mutex threadMutex;
DWORD status;
HANDLE deletionEvent = CreateEventW(nullptr, FALSE, FALSE, NULL); HANDLE deletionEvent = CreateEventW(nullptr, FALSE, FALSE, NULL);
std::thread flushThread = std::thread([&] { while (WaitForSingleObject(deletionEvent, 10) == WAIT_TIMEOUT) Flush(); }); std::thread flushThread = std::thread([&] { while (WaitForSingleObject(deletionEvent, 10) == WAIT_TIMEOUT) Flush(); });

View File

@ -53,6 +53,14 @@ namespace
} }
RCode.remove(0, 1); 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] // [*deref_offset|0]
if (RCode.at(0).unicode() == L'0') RCode.remove(0, 1); // Legacy if (RCode.at(0).unicode() == L'0') RCode.remove(0, 1); // Legacy
QRegularExpressionMatch deref = QRegularExpression("^\\*(\\-?[[:xdigit:]]+)").match(RCode); QRegularExpressionMatch deref = QRegularExpression("^\\*(\\-?[[:xdigit:]]+)").match(RCode);
@ -109,6 +117,14 @@ namespace
HCode.remove(0, 1); 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 // data_offset
QRegularExpressionMatch dataOffset = QRegularExpression("^\\-?[[:xdigit:]]+").match(HCode); QRegularExpressionMatch dataOffset = QRegularExpression("^\\-?[[:xdigit:]]+").match(HCode);
if (!dataOffset.hasMatch()) return {}; if (!dataOffset.hasMatch()) return {};
@ -164,12 +180,31 @@ namespace
return hp; 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 GenerateHCode(HookParam hp, DWORD processId)
{ {
QString HCode = "/H"; QString HCode = "/H";
QTextStream codeBuilder(&HCode); QTextStream codeBuilder(&HCode);
codeBuilder.setIntegerBase(16);
codeBuilder.setNumberFlags(QTextStream::UppercaseDigits);
if (hp.type & USING_UNICODE) if (hp.type & USING_UNICODE)
{ {
@ -185,6 +220,11 @@ namespace
} }
if (hp.type & NO_CONTEXT) codeBuilder << "N"; 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.offset < 0) hp.offset += 4;
if (hp.split < 0) hp.split += 4; if (hp.split < 0) hp.split += 4;
@ -214,24 +254,6 @@ namespace
return HCode; 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<HookParam> ParseCode(QString code) std::optional<HookParam> ParseCode(QString code)

View File

@ -13,11 +13,11 @@ QString GenerateCode(HookParam hp, DWORD processId);
static QString CodeInfoDump = static QString CodeInfoDump =
"Enter hook code\r\n\ "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\ OR\r\n\
Enter read code\r\n\ Enter read code\r\n\
/R{S|Q|V}[*deref_offset|0]@addr\r\n\ /R{S|Q|V}[#codepage][*deref_offset|0]@addr\r\n\
All numbers in hexadecimal\r\n\ All numbers except codepage in hexadecimal\r\n\
A/B: Shift-JIS char little/big endian\r\n\ A/B: Shift-JIS char little/big endian\r\n\
W: UTF-16 char\r\n\ W: UTF-16 char\r\n\
S/Q/V: Shift-JIS/UTF-16/UTF-8 string\r\n\ S/Q/V: Shift-JIS/UTF-16/UTF-8 string\r\n\

View File

@ -3,7 +3,7 @@
static int TESTS = [] 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("/HB4@0"));
assert(ParseCode("/RS*10@44")); assert(ParseCode("/RS*10@44"));
assert(!ParseCode("HQ@4")); assert(!ParseCode("HQ@4"));

View File

@ -20,6 +20,7 @@ struct HookParam
wchar_t module[MAX_MODULE_SIZE]; wchar_t module[MAX_MODULE_SIZE];
char function[MAX_MODULE_SIZE]; char function[MAX_MODULE_SIZE];
DWORD type; // flags DWORD type; // flags
UINT codepage; // text encoding
WORD length_offset; // index of the string length WORD length_offset; // index of the string length
DWORD user_value; // 7/20/2014: jichi additional parameters for PSP games DWORD user_value; // 7/20/2014: jichi additional parameters for PSP games

View File

@ -232,13 +232,16 @@ bool TextHook::InsertHookCode()
bool TextHook::UnsafeInsertHookCode() 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 (hp.type & FUNCTION_OFFSET)
if (FARPROC function = GetProcAddress(GetModuleHandleW(hp.module), hp.function)) hp.insertion_address += (uint64_t)function; 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 return ConsoleOutput("Textractor: UnsafeInsertHookCode: FAILED: function not present"), false;
else if (HMODULE moduleBase = GetModuleHandleW(hp.module)) hp.insertion_address += (uint64_t)moduleBase; else if (HMODULE moduleBase = GetModuleHandleW(hp.module)) hp.insertion_address += (uint64_t)moduleBase;
else return ConsoleOutput("Textractor: UnsafeInsertHookCode: FAILED: module not present"), false; 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; BYTE* original;
insert: insert:
if (MH_STATUS err = MH_CreateHook((void*)hp.insertion_address, (void*)trampoline, (void**)&original)) if (MH_STATUS err = MH_CreateHook((void*)hp.insertion_address, (void*)trampoline, (void**)&original))