forked from Public-Mirror/Textractor
Merge pull request #149 from Artikash/remove-hooks
Remove hooks feature
This commit is contained in:
commit
58c80c82ba
@ -16,7 +16,6 @@ namespace
|
||||
{
|
||||
public:
|
||||
ProcessRecord(DWORD processId, HANDLE pipe) :
|
||||
processId(processId),
|
||||
pipe(pipe),
|
||||
mappedFile(OpenFileMappingW(FILE_MAP_READ, FALSE, (ITH_SECTION_ + std::to_wstring(processId)).c_str())),
|
||||
view(*(const TextHook(*)[MAX_HOOK])MapViewOfFile(mappedFile, FILE_MAP_READ, 0, 0, HOOK_SECTION_SIZE / 2)), // jichi 1/16/2015: Changed to half to hook section size
|
||||
@ -47,23 +46,19 @@ namespace
|
||||
}).detach();
|
||||
}
|
||||
|
||||
Host::HookEventHandler OnHookFound = [](HookParam hp, DWORD processId, const std::wstring& text)
|
||||
Host::HookEventHandler OnHookFound = [](HookParam hp, const std::wstring& text)
|
||||
{
|
||||
Host::AddConsoleOutput(Util::GenerateCode(hp, 0) + L": " + text);
|
||||
};
|
||||
|
||||
private:
|
||||
DWORD processId;
|
||||
HANDLE pipe;
|
||||
AutoHandle<> mappedFile;
|
||||
const TextHook(&view)[MAX_HOOK];
|
||||
WinMutex viewMutex;
|
||||
};
|
||||
|
||||
size_t HashThreadParam(ThreadParam tp)
|
||||
{
|
||||
return std::hash<int64_t>()(tp.processId + tp.addr) + std::hash<int64_t>()(tp.ctx + tp.ctx2);
|
||||
}
|
||||
size_t HashThreadParam(ThreadParam tp) { return std::hash<int64_t>()(tp.processId + tp.addr) + std::hash<int64_t>()(tp.ctx + tp.ctx2); }
|
||||
Synchronized<std::unordered_map<ThreadParam, TextThread, Functor<HashThreadParam>>, std::recursive_mutex> textThreadsByParams;
|
||||
Synchronized<std::unordered_map<DWORD, ProcessRecord>, std::recursive_mutex> processRecordsByIds;
|
||||
|
||||
@ -109,13 +104,13 @@ namespace
|
||||
auto info = *(HookFoundNotif*)buffer;
|
||||
auto& OnHookFound = processRecordsByIds->at(processId).OnHookFound;
|
||||
std::wstring wide = info.text;
|
||||
if (wide.size() > STRING) OnHookFound(info.hp, processId, info.text);
|
||||
if (wide.size() > STRING) OnHookFound(info.hp, info.text);
|
||||
info.hp.type = USING_STRING;
|
||||
if (auto converted = Util::StringToWideString((char*)info.text, Host::defaultCodepage))
|
||||
if (converted->size() > STRING) OnHookFound(info.hp, processId, converted.value());
|
||||
if (converted->size() > STRING) OnHookFound(info.hp, converted.value());
|
||||
info.hp.codepage = CP_UTF8;
|
||||
if (auto converted = Util::StringToWideString((char*)info.text, CP_UTF8))
|
||||
if (converted->size() > STRING) OnHookFound(info.hp, processId, converted.value());
|
||||
if (converted->size() > STRING) OnHookFound(info.hp, converted.value());
|
||||
}
|
||||
break;
|
||||
case HOST_NOTIFICATION_RMVHOOK:
|
||||
@ -134,13 +129,14 @@ namespace
|
||||
{
|
||||
auto tp = *(ThreadParam*)buffer;
|
||||
auto textThreadsByParams = ::textThreadsByParams.Acquire();
|
||||
if (textThreadsByParams->count(tp) == 0) try
|
||||
auto textThread = textThreadsByParams->find(tp);
|
||||
if (textThread == textThreadsByParams->end())
|
||||
{
|
||||
TextThread& created = textThreadsByParams->try_emplace(tp, tp, Host::GetHookParam(tp)).first->second;
|
||||
OnCreate(created);
|
||||
try { textThread = textThreadsByParams->try_emplace(tp, tp, Host::GetHookParam(tp)).first; }
|
||||
catch (std::out_of_range) { continue; } // probably garbage data in pipe, try again
|
||||
OnCreate(textThread->second);
|
||||
}
|
||||
catch (std::out_of_range) { continue; } // probably garbage data in pipe, try again
|
||||
textThreadsByParams->find(tp)->second.Push(buffer + sizeof(tp), bytesRead - sizeof(tp));
|
||||
textThread->second.Push(buffer + sizeof(tp), bytesRead - sizeof(tp));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -226,6 +222,11 @@ namespace Host
|
||||
processRecordsByIds->at(processId).Send(InsertHookCmd(hp));
|
||||
}
|
||||
|
||||
void RemoveHook(DWORD processId, uint64_t address)
|
||||
{
|
||||
processRecordsByIds->at(processId).Send(RemoveHookCmd(address));
|
||||
}
|
||||
|
||||
void FindHooks(DWORD processId, SearchParam sp, HookEventHandler HookFound)
|
||||
{
|
||||
if (HookFound) processRecordsByIds->at(processId).OnHookFound = HookFound;
|
||||
|
@ -7,17 +7,18 @@ namespace Host
|
||||
{
|
||||
using ProcessEventHandler = std::function<void(DWORD)>;
|
||||
using ThreadEventHandler = std::function<void(TextThread&)>;
|
||||
using HookEventHandler = std::function<void(HookParam, DWORD processId, const std::wstring& text)>;
|
||||
using HookEventHandler = std::function<void(HookParam, const std::wstring& text)>;
|
||||
void Start(ProcessEventHandler Connect, ProcessEventHandler Disconnect, ThreadEventHandler Create, ThreadEventHandler Destroy, TextThread::OutputCallback Output);
|
||||
|
||||
void InjectProcess(DWORD processId);
|
||||
void DetachProcess(DWORD processId);
|
||||
void InsertHook(DWORD processId, HookParam hp);
|
||||
void RemoveHook(DWORD processId, uint64_t address);
|
||||
void FindHooks(DWORD processId, SearchParam sp, HookEventHandler HookFound = {});
|
||||
|
||||
HookParam GetHookParam(ThreadParam tp);
|
||||
|
||||
TextThread& GetThread(ThreadParam tp);
|
||||
|
||||
void AddConsoleOutput(std::wstring text);
|
||||
|
||||
inline int defaultCodepage = SHIFT_JIS;
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
#include <QListWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QInputDialog>
|
||||
#include <QFileDialog>
|
||||
@ -18,6 +19,7 @@ extern const char* ATTACH;
|
||||
extern const char* LAUNCH;
|
||||
extern const char* DETACH;
|
||||
extern const char* ADD_HOOK;
|
||||
extern const char* REMOVE_HOOKS;
|
||||
extern const char* SAVE_HOOKS;
|
||||
extern const char* FIND_HOOKS;
|
||||
extern const char* SETTINGS;
|
||||
@ -38,6 +40,7 @@ extern const char* HOOK_SEARCH_FILTER;
|
||||
extern const char* START_HOOK_SEARCH;
|
||||
extern const char* SAVE_SEARCH_RESULTS;
|
||||
extern const char* TEXT_FILES;
|
||||
extern const char* DOUBLE_CLICK_TO_REMOVE_HOOK;
|
||||
extern const char* SAVE_SETTINGS;
|
||||
extern const char* USE_JP_LOCALE;
|
||||
extern const char* FILTER_REPETITION;
|
||||
@ -61,6 +64,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
{ LAUNCH, &MainWindow::LaunchProcess },
|
||||
{ DETACH, &MainWindow::DetachProcess },
|
||||
{ ADD_HOOK, &MainWindow::AddHook },
|
||||
{ REMOVE_HOOKS, &MainWindow::RemoveHooks },
|
||||
{ SAVE_HOOKS, &MainWindow::SaveHooks },
|
||||
{ FIND_HOOKS, &MainWindow::FindHooks },
|
||||
{ SETTINGS, &MainWindow::Settings },
|
||||
@ -267,6 +271,7 @@ void MainWindow::LaunchProcess()
|
||||
|
||||
PROCESS_INFORMATION info = {};
|
||||
if (QMessageBox::question(this, SELECT_PROCESS, USE_JP_LOCALE) == QMessageBox::Yes)
|
||||
{
|
||||
if (HMODULE localeEmulator = LoadLibraryOnce(L"LoaderDll"))
|
||||
{
|
||||
// see https://github.com/xupefei/Locale-Emulator/blob/aa99dec3b25708e676c90acf5fed9beaac319160/LEProc/LoaderWrapper.cs#L252
|
||||
@ -285,6 +290,7 @@ void MainWindow::LaunchProcess()
|
||||
((LONG(__stdcall*)(decltype(&LEB), LPCWSTR appName, LPWSTR commandLine, LPCWSTR currentDir, void*, void*, PROCESS_INFORMATION*, void*, void*, void*, void*))
|
||||
GetProcAddress(localeEmulator, "LeCreateProcess"))(&LEB, process.c_str(), NULL, path.c_str(), NULL, NULL, &info, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
}
|
||||
if (info.hProcess == NULL)
|
||||
{
|
||||
STARTUPINFOW DUMMY = { sizeof(DUMMY) };
|
||||
@ -308,6 +314,33 @@ void MainWindow::AddHook()
|
||||
else Host::AddConsoleOutput(INVALID_CODE);
|
||||
}
|
||||
|
||||
void MainWindow::RemoveHooks()
|
||||
{
|
||||
DWORD processId = GetSelectedProcessId();
|
||||
std::unordered_map<uint64_t, HookParam> hooks;
|
||||
for (int i = 0; i < ui->ttCombo->count(); ++i)
|
||||
{
|
||||
ThreadParam tp = ParseTextThreadString(ui->ttCombo->itemText(i));
|
||||
if (tp.processId == GetSelectedProcessId()) hooks[tp.addr] = Host::GetHookParam(tp);
|
||||
}
|
||||
auto hookList = new QListWidget(this);
|
||||
hookList->setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint);
|
||||
hookList->setMinimumSize({ 300, 50 });
|
||||
hookList->setWindowTitle(DOUBLE_CLICK_TO_REMOVE_HOOK);
|
||||
for (auto[address, hp] : hooks)
|
||||
new QListWidgetItem(QString(hp.name) + "@" + QString::number(address, 16), hookList);
|
||||
connect(hookList, &QListWidget::itemDoubleClicked, [processId, hookList](QListWidgetItem* item)
|
||||
{
|
||||
try
|
||||
{
|
||||
Host::RemoveHook(processId, item->text().split("@")[1].toULongLong(nullptr, 16));
|
||||
delete item;
|
||||
}
|
||||
catch (std::out_of_range) { hookList->close(); }
|
||||
});
|
||||
hookList->show();
|
||||
}
|
||||
|
||||
void MainWindow::SaveHooks()
|
||||
{
|
||||
if (auto processName = Util::GetModuleFilename(GetSelectedProcessId()))
|
||||
@ -378,7 +411,8 @@ void MainWindow::FindHooks()
|
||||
if (!filterInput->text().isEmpty()) try { filter = std::wregex(S(filterInput->text())); } catch (std::regex_error) {};
|
||||
memcpy(sp.pattern, pattern.data(), sp.length = min(pattern.size(), 25));
|
||||
auto hooks = std::make_shared<QString>();
|
||||
Host::FindHooks(processId, sp, [hooks, filter](HookParam hp, DWORD processId, const std::wstring& text)
|
||||
DWORD processId = this->processId;
|
||||
Host::FindHooks(processId, sp, [processId, hooks, filter](HookParam hp, const std::wstring& text)
|
||||
{
|
||||
if (std::regex_search(text, filter)) hooks->append(S(Util::GenerateCode(hp, processId)) + ": " + S(text) + "\n");
|
||||
});
|
||||
|
@ -36,6 +36,7 @@ private:
|
||||
void LaunchProcess();
|
||||
void DetachProcess();
|
||||
void AddHook();
|
||||
void RemoveHooks();
|
||||
void SaveHooks();
|
||||
void FindHooks();
|
||||
void Settings();
|
||||
|
@ -31,7 +31,7 @@ constexpr bool x64 = false;
|
||||
|
||||
template <typename T> using Array = T[];
|
||||
|
||||
template<typename E, typename M = std::mutex>
|
||||
template<typename T, typename M = std::mutex>
|
||||
class Synchronized
|
||||
{
|
||||
public:
|
||||
@ -40,17 +40,17 @@ public:
|
||||
|
||||
struct Locker
|
||||
{
|
||||
E* operator->() { return ptr; }
|
||||
T* operator->() { return &contents; }
|
||||
std::unique_lock<M> lock;
|
||||
E* ptr;
|
||||
T& contents;
|
||||
};
|
||||
|
||||
Locker Acquire() { return { std::unique_lock(mtx), &contents }; }
|
||||
Locker Acquire() { return { std::unique_lock(m), contents }; }
|
||||
Locker operator->() { return Acquire(); }
|
||||
|
||||
private:
|
||||
E contents;
|
||||
M mtx;
|
||||
T contents;
|
||||
M m;
|
||||
};
|
||||
|
||||
template <auto F>
|
||||
|
@ -27,5 +27,5 @@ enum HookParamType : unsigned
|
||||
FIXING_SPLIT = 0x1000,
|
||||
DIRECT_READ = 0x2000, // /R read code instead of classic /H hook code
|
||||
HOOK_ENGINE = 0x4000,
|
||||
HOOK_ADDITIONAL = 0x8000
|
||||
HOOK_ADDITIONAL = 0x8000,
|
||||
};
|
||||
|
@ -75,6 +75,13 @@ struct InsertHookCmd // From host
|
||||
HookParam hp;
|
||||
};
|
||||
|
||||
struct RemoveHookCmd // From host
|
||||
{
|
||||
RemoveHookCmd(uint64_t address) : address(address) {}
|
||||
HostCommandType command = HOST_COMMAND_REMOVE_HOOK;
|
||||
uint64_t address;
|
||||
};
|
||||
|
||||
struct FindHookCmd // From host
|
||||
{
|
||||
FindHookCmd(SearchParam sp) : sp(sp) {}
|
||||
|
2
text.cpp
2
text.cpp
@ -10,6 +10,7 @@ const char* ATTACH = u8"Attach to game";
|
||||
const char* LAUNCH = u8"Launch game";
|
||||
const char* DETACH = u8"Detach from game";
|
||||
const char* ADD_HOOK = u8"Add hook";
|
||||
const char* REMOVE_HOOKS = u8"Remove hook(s)";
|
||||
const char* SAVE_HOOKS = u8"Save hook(s)";
|
||||
const char* FIND_HOOKS = u8"Find hooks";
|
||||
const char* SETTINGS = u8"Settings";
|
||||
@ -60,6 +61,7 @@ const char* HOOK_SEARCH_FILTER = u8"Results must match this regex";
|
||||
const char* START_HOOK_SEARCH = u8"Start hook search";
|
||||
const char* SAVE_SEARCH_RESULTS = u8"Save search results";
|
||||
const char* TEXT_FILES = u8"Text (*.txt)";
|
||||
const char* DOUBLE_CLICK_TO_REMOVE_HOOK = u8"Double click a hook to remove it";
|
||||
const char* FILTER_REPETITION = u8"Repetition Filter";
|
||||
const char* DEFAULT_CODEPAGE = u8"Default Codepage";
|
||||
const char* FLUSH_DELAY = u8"Flush Delay";
|
||||
|
@ -61,7 +61,14 @@ DWORD WINAPI Pipe(LPVOID)
|
||||
case HOST_COMMAND_NEW_HOOK:
|
||||
{
|
||||
auto info = *(InsertHookCmd*)buffer;
|
||||
NewHook(info.hp, "UserHook", 0);
|
||||
static int userHooks = 0;
|
||||
NewHook(info.hp, ("UserHook" + std::to_string(userHooks += 1)).c_str(), 0);
|
||||
}
|
||||
break;
|
||||
case HOST_COMMAND_REMOVE_HOOK:
|
||||
{
|
||||
auto info = *(RemoveHookCmd*)buffer;
|
||||
RemoveHook(info.address, 0);
|
||||
}
|
||||
break;
|
||||
case HOST_COMMAND_FIND_HOOK:
|
||||
@ -180,8 +187,12 @@ void NewHook(HookParam hp, LPCSTR lpname, DWORD flag)
|
||||
if (++currentHook >= MAX_HOOK) return ConsoleOutput(TOO_MANY_HOOKS);
|
||||
if (lpname && *lpname) strncpy_s(hp.name, lpname, HOOK_NAME_SIZE - 1);
|
||||
ConsoleOutput(INSERTING_HOOK, hp.name);
|
||||
if (hp.address) RemoveHook(hp.address, 0);
|
||||
if (!(*hooks)[currentHook].Insert(hp, flag)) ConsoleOutput(HOOK_FAILED);
|
||||
RemoveHook(hp.address, 0);
|
||||
if (!(*hooks)[currentHook].Insert(hp, flag))
|
||||
{
|
||||
ConsoleOutput(HOOK_FAILED);
|
||||
(*hooks)[currentHook].Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -286,6 +286,7 @@ void TextHook::RemoveReadCode()
|
||||
void TextHook::Clear()
|
||||
{
|
||||
std::scoped_lock lock(viewMutex);
|
||||
if (address == 0) return;
|
||||
if (hp.type & DIRECT_READ) RemoveReadCode();
|
||||
else RemoveHookCode();
|
||||
NotifyHookRemove(address, hp.name);
|
||||
|
Loading…
x
Reference in New Issue
Block a user