holy shit you can overload operator-> to do WHAT??
This commit is contained in:
parent
24e31247af
commit
efa8d26ada
@ -1,7 +1,10 @@
|
||||
#include "extenwindow.h"
|
||||
#include "ui_extenwindow.h"
|
||||
#include "text.h"
|
||||
#include "defs.h"
|
||||
#include "types.h"
|
||||
#include "misc.h"
|
||||
#include <shared_mutex>
|
||||
#include <QFileDialog>
|
||||
#include <QMimeData>
|
||||
#include <QUrl>
|
||||
@ -81,12 +84,7 @@ ExtenWindow::ExtenWindow(QWidget* parent) :
|
||||
extenList = findChild<QListWidget*>("extenList");
|
||||
extenList->installEventFilter(this);
|
||||
|
||||
if (extensions.empty())
|
||||
{
|
||||
extenSaveFile.open(QIODevice::ReadOnly);
|
||||
for (auto extenName : QString(extenSaveFile.readAll()).split(">")) Load(extenName);
|
||||
extenSaveFile.close();
|
||||
}
|
||||
for (auto extenName : QString(QAutoFile(EXTEN_SAVE_FILE, QIODevice::ReadOnly)->readAll()).split(">")) Load(extenName);
|
||||
Sync();
|
||||
}
|
||||
|
||||
@ -98,14 +96,13 @@ ExtenWindow::~ExtenWindow()
|
||||
void ExtenWindow::Sync()
|
||||
{
|
||||
extenList->clear();
|
||||
extenSaveFile.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
QAutoFile extenSaveFile(EXTEN_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
std::shared_lock sharedLock(extenMutex);
|
||||
for (auto extenName : extenNames)
|
||||
{
|
||||
extenList->addItem(extenName);
|
||||
extenSaveFile.write((extenName + ">").toUtf8());
|
||||
extenSaveFile->write((extenName + ">").toUtf8());
|
||||
}
|
||||
extenSaveFile.close();
|
||||
}
|
||||
|
||||
void ExtenWindow::Add(QString fileName)
|
||||
|
@ -2,8 +2,6 @@
|
||||
#define EXTENSIONS_H
|
||||
|
||||
#include "qtcommon.h"
|
||||
#include "defs.h"
|
||||
#include <shared_mutex>
|
||||
#include <QListWidget>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QDropEvent>
|
||||
@ -35,7 +33,6 @@ private:
|
||||
void dropEvent(QDropEvent* event);
|
||||
|
||||
Ui::ExtenWindow* ui;
|
||||
QFile extenSaveFile = QFile(EXTEN_SAVE_FILE);
|
||||
QListWidget* extenList;
|
||||
};
|
||||
|
||||
|
@ -10,86 +10,58 @@ namespace
|
||||
class ProcessRecord
|
||||
{
|
||||
public:
|
||||
ProcessRecord(DWORD processId, HANDLE hostPipe) :
|
||||
hostPipe(hostPipe),
|
||||
section(OpenFileMappingW(FILE_MAP_READ, FALSE, (ITH_SECTION_ + std::to_wstring(processId)).c_str())),
|
||||
sectionMap(MapViewOfFile(section, FILE_MAP_READ, 0, 0, HOOK_SECTION_SIZE / 2)), // jichi 1/16/2015: Changed to half to hook section size
|
||||
sectionMutex(ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId))
|
||||
{}
|
||||
inline static Host::ProcessEventCallback OnConnect, OnDisconnect;
|
||||
|
||||
ProcessRecord(ProcessRecord&) = delete;
|
||||
ProcessRecord& operator=(ProcessRecord) = delete;
|
||||
ProcessRecord(DWORD processId, HANDLE pipe) :
|
||||
processId(processId),
|
||||
pipe(pipe),
|
||||
fileMapping(OpenFileMappingW(FILE_MAP_READ, FALSE, (ITH_SECTION_ + std::to_wstring(processId)).c_str())),
|
||||
mappedView(MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, HOOK_SECTION_SIZE / 2)), // jichi 1/16/2015: Changed to half to hook section size
|
||||
sectionMutex(ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId))
|
||||
{
|
||||
OnConnect(processId);
|
||||
}
|
||||
|
||||
~ProcessRecord()
|
||||
{
|
||||
UnmapViewOfFile(sectionMap);
|
||||
CloseHandle(section);
|
||||
OnDisconnect(processId);
|
||||
UnmapViewOfFile(mappedView);
|
||||
}
|
||||
|
||||
TextHook GetHook(uint64_t addr)
|
||||
{
|
||||
if (sectionMap == nullptr) return {};
|
||||
if (mappedView == nullptr) return {};
|
||||
LOCK(sectionMutex);
|
||||
auto hooks = (const TextHook*)sectionMap;
|
||||
auto hooks = (const TextHook*)mappedView;
|
||||
for (int i = 0; i < MAX_HOOK; ++i)
|
||||
if (hooks[i].hp.insertion_address == addr) return hooks[i];
|
||||
return {};
|
||||
}
|
||||
|
||||
HANDLE hostPipe;
|
||||
template <typename T>
|
||||
void Send(T data)
|
||||
{
|
||||
DWORD DUMMY;
|
||||
WriteFile(pipe, &data, sizeof(data), &DUMMY, nullptr);
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE section;
|
||||
LPVOID sectionMap;
|
||||
DWORD processId;
|
||||
HANDLE pipe;
|
||||
AutoHandle<> fileMapping;
|
||||
LPCVOID mappedView;
|
||||
WinMutex sectionMutex;
|
||||
};
|
||||
|
||||
ThreadEventCallback OnCreate, OnDestroy;
|
||||
ProcessEventCallback OnAttach, OnDetach;
|
||||
ThreadSafePtr<std::unordered_map<ThreadParam, std::shared_ptr<TextThread>>> textThreadsByParams;
|
||||
ThreadSafePtr<std::unordered_map<DWORD, std::unique_ptr<ProcessRecord>>> processRecordsByIds;
|
||||
|
||||
std::unordered_map<ThreadParam, std::shared_ptr<TextThread>> textThreadsByParams;
|
||||
std::unordered_map<DWORD, std::unique_ptr<ProcessRecord>> processRecordsByIds;
|
||||
|
||||
std::recursive_mutex hostMutex;
|
||||
|
||||
DWORD DUMMY;
|
||||
ThreadParam CONSOLE{ 0, -1ULL, -1ULL, -1ULL }, CLIPBOARD{ 0, 0, -1ULL, -1ULL };
|
||||
|
||||
void DispatchText(ThreadParam tp, const BYTE* text, int len)
|
||||
{
|
||||
LOCK(hostMutex);
|
||||
if (textThreadsByParams[tp] == nullptr)
|
||||
{
|
||||
if (textThreadsByParams.size() > MAX_THREAD_COUNT) return Host::AddConsoleOutput(TOO_MANY_THREADS);
|
||||
OnCreate(textThreadsByParams[tp] = std::make_shared<TextThread>(tp, Host::GetHookParam(tp), Host::GetHookName(tp)));
|
||||
}
|
||||
textThreadsByParams[tp]->Push(text, len);
|
||||
}
|
||||
|
||||
void RemoveThreads(std::function<bool(ThreadParam)> removeIf)
|
||||
{
|
||||
LOCK(hostMutex);
|
||||
for (auto it = textThreadsByParams.begin(); it != textThreadsByParams.end();)
|
||||
if (auto curr = it++; removeIf(curr->first))
|
||||
{
|
||||
OnDestroy(curr->second);
|
||||
textThreadsByParams.erase(curr->first);
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterProcess(DWORD processId, HANDLE hostPipe)
|
||||
{
|
||||
LOCK(hostMutex);
|
||||
processRecordsByIds.insert({ processId, std::make_unique<ProcessRecord>(processId, hostPipe) });
|
||||
OnAttach(processId);
|
||||
}
|
||||
|
||||
void UnregisterProcess(DWORD processId)
|
||||
{
|
||||
OnDetach(processId);
|
||||
LOCK(hostMutex);
|
||||
processRecordsByIds.erase(processId);
|
||||
RemoveThreads([&](ThreadParam tp) { return tp.processId == processId; });
|
||||
auto lockedTextThreadsByParams = textThreadsByParams.operator->();
|
||||
for (auto it = lockedTextThreadsByParams->begin(); it != lockedTextThreadsByParams->end(); removeIf(it->first) ? it = lockedTextThreadsByParams->erase(it) : ++it);
|
||||
}
|
||||
|
||||
void CreatePipe()
|
||||
@ -100,14 +72,15 @@ namespace
|
||||
InitializeSecurityDescriptor(&pipeSD, SECURITY_DESCRIPTOR_REVISION);
|
||||
SetSecurityDescriptorDacl(&pipeSD, TRUE, NULL, FALSE); // Allow non-admin processes to connect to pipe created by admin host
|
||||
SECURITY_ATTRIBUTES pipeSA = { sizeof(SECURITY_ATTRIBUTES), &pipeSD, FALSE };
|
||||
HANDLE hookPipe = CreateNamedPipeW(HOOK_PIPE, PIPE_ACCESS_INBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, 0, PIPE_BUFFER_SIZE, MAXDWORD, &pipeSA);
|
||||
HANDLE hostPipe = CreateNamedPipeW(HOST_PIPE, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_BUFFER_SIZE, 0, MAXDWORD, &pipeSA);
|
||||
AutoHandle<Util::NamedPipeHandleCloser>
|
||||
hookPipe = CreateNamedPipeW(HOOK_PIPE, PIPE_ACCESS_INBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, 0, PIPE_BUFFER_SIZE, MAXDWORD, &pipeSA),
|
||||
hostPipe = CreateNamedPipeW(HOST_PIPE, PIPE_ACCESS_OUTBOUND, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, PIPE_UNLIMITED_INSTANCES, PIPE_BUFFER_SIZE, 0, MAXDWORD, &pipeSA);
|
||||
ConnectNamedPipe(hookPipe, nullptr);
|
||||
|
||||
BYTE buffer[PIPE_BUFFER_SIZE] = {};
|
||||
DWORD bytesRead, processId;
|
||||
ReadFile(hookPipe, &processId, sizeof(processId), &bytesRead, nullptr);
|
||||
RegisterProcess(processId, hostPipe);
|
||||
processRecordsByIds->insert({ processId, std::make_unique<ProcessRecord>(processId, hostPipe) });
|
||||
|
||||
CreatePipe();
|
||||
|
||||
@ -129,16 +102,19 @@ namespace
|
||||
default:
|
||||
{
|
||||
auto tp = *(ThreadParam*)buffer;
|
||||
DispatchText(tp, buffer + sizeof(tp), bytesRead - sizeof(tp));
|
||||
if (textThreadsByParams->count(tp) == 0)
|
||||
{
|
||||
auto textThread = textThreadsByParams->insert({ tp, std::make_shared<TextThread>(tp, Host::GetHookParam(tp), Host::GetHookName(tp)) }).first->second;
|
||||
if (textThreadsByParams->size() > MAX_THREAD_COUNT) Host::AddConsoleOutput(TOO_MANY_THREADS);
|
||||
else textThread->Start();
|
||||
}
|
||||
textThreadsByParams->at(tp)->Push(buffer + sizeof(tp), bytesRead - sizeof(tp));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
UnregisterProcess(processId);
|
||||
DisconnectNamedPipe(hookPipe);
|
||||
DisconnectNamedPipe(hostPipe);
|
||||
CloseHandle(hookPipe);
|
||||
CloseHandle(hostPipe);
|
||||
RemoveThreads([&](ThreadParam tp) { return tp.processId == processId; });
|
||||
processRecordsByIds->erase(processId);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
@ -156,12 +132,16 @@ namespace
|
||||
|
||||
namespace Host
|
||||
{
|
||||
void Start(ProcessEventCallback onAttach, ProcessEventCallback onDetach, ThreadEventCallback onCreate, ThreadEventCallback onDestroy, TextThread::OutputCallback output)
|
||||
void Start(ProcessEventCallback OnConnect, ProcessEventCallback OnDisconnect, TextThread::EventCallback OnCreate, TextThread::EventCallback OnDestroy, TextThread::OutputCallback Output)
|
||||
{
|
||||
OnAttach = onAttach; OnDetach = onDetach; OnCreate = onCreate; OnDestroy = onDestroy; TextThread::Output = output;
|
||||
RegisterProcess(CONSOLE.processId, INVALID_HANDLE_VALUE);
|
||||
OnCreate(textThreadsByParams[CONSOLE] = std::make_shared<TextThread>(CONSOLE, HookParam{}, L"Console"));
|
||||
OnCreate(textThreadsByParams[CLIPBOARD] = std::make_shared<TextThread>(CLIPBOARD, HookParam{}, L"Clipboard"));
|
||||
ProcessRecord::OnConnect = OnConnect;
|
||||
ProcessRecord::OnDisconnect = OnDisconnect;
|
||||
TextThread::OnCreate = OnCreate;
|
||||
TextThread::OnDestroy = OnDestroy;
|
||||
TextThread::Output = Output;
|
||||
processRecordsByIds->insert({ CONSOLE.processId, std::make_unique<ProcessRecord>(CONSOLE.processId, INVALID_HANDLE_VALUE) });
|
||||
textThreadsByParams->insert({ CONSOLE, std::make_shared<TextThread>(CONSOLE, HookParam{}, L"Console") });
|
||||
textThreadsByParams->insert({ CLIPBOARD, std::make_shared<TextThread>(CLIPBOARD, HookParam{}, L"Clipboard") });
|
||||
StartCapturingClipboard();
|
||||
CreatePipe();
|
||||
}
|
||||
@ -170,9 +150,10 @@ namespace Host
|
||||
{
|
||||
// Artikash 7/25/2018: This is only called when Textractor is closed, at which point Windows should free everything itself...right?
|
||||
#ifdef _DEBUG // Check memory leaks
|
||||
LOCK(hostMutex);
|
||||
processRecordsByIds.clear();
|
||||
textThreadsByParams.clear();
|
||||
ProcessRecord::OnConnect = ProcessRecord::OnDisconnect = [](auto) {};
|
||||
TextThread::OnCreate = TextThread::OnDestroy = [](auto) {};
|
||||
processRecordsByIds->clear();
|
||||
textThreadsByParams->clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -180,43 +161,37 @@ namespace Host
|
||||
{
|
||||
if (processId == GetCurrentProcessId()) return false;
|
||||
|
||||
CloseHandle(CreateMutexW(nullptr, FALSE, (ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId)).c_str()));
|
||||
WinMutex(ITH_HOOKMAN_MUTEX_ + std::to_wstring(processId));
|
||||
if (GetLastError() == ERROR_ALREADY_EXISTS)
|
||||
{
|
||||
AddConsoleOutput(ALREADY_INJECTED);
|
||||
return false;
|
||||
}
|
||||
|
||||
HMODULE textHooker = LoadLibraryExW(ITH_DLL, nullptr, DONT_RESOLVE_DLL_REFERENCES);
|
||||
wchar_t textHookerPath[MAX_PATH];
|
||||
DWORD textHookerPathSize = GetModuleFileNameW(textHooker, textHookerPath, MAX_PATH) * 2 + 2;
|
||||
FreeLibrary(textHooker);
|
||||
static HMODULE vnrhook = LoadLibraryExW(ITH_DLL, nullptr, DONT_RESOLVE_DLL_REFERENCES);
|
||||
static std::wstring location = Util::GetModuleFileName(vnrhook).value();
|
||||
|
||||
if (HANDLE processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId))
|
||||
if (AutoHandle<> process = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId))
|
||||
{
|
||||
#ifdef _WIN64
|
||||
BOOL invalidProcess = FALSE;
|
||||
IsWow64Process(processHandle, &invalidProcess);
|
||||
IsWow64Process(process, &invalidProcess);
|
||||
if (invalidProcess)
|
||||
{
|
||||
AddConsoleOutput(ARCHITECTURE_MISMATCH);
|
||||
CloseHandle(processHandle);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (LPVOID remoteData = VirtualAllocEx(processHandle, nullptr, textHookerPathSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))
|
||||
if (LPVOID remoteData = VirtualAllocEx(process, nullptr, location.size() * 2 + 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))
|
||||
{
|
||||
WriteProcessMemory(processHandle, remoteData, textHookerPath, textHookerPathSize, nullptr);
|
||||
if (HANDLE thread = CreateRemoteThread(processHandle, nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, remoteData, 0, nullptr))
|
||||
WriteProcessMemory(process, remoteData, location.c_str(), location.size() * 2 + 2, nullptr);
|
||||
if (AutoHandle<> thread = CreateRemoteThread(process, nullptr, 0, (LPTHREAD_START_ROUTINE)LoadLibraryW, remoteData, 0, nullptr))
|
||||
{
|
||||
WaitForSingleObject(thread, timeout);
|
||||
CloseHandle(thread);
|
||||
VirtualFreeEx(processHandle, remoteData, 0, MEM_RELEASE);
|
||||
CloseHandle(processHandle);
|
||||
VirtualFreeEx(process, remoteData, 0, MEM_RELEASE);
|
||||
return true;
|
||||
}
|
||||
VirtualFreeEx(processHandle, remoteData, 0, MEM_RELEASE);
|
||||
CloseHandle(processHandle);
|
||||
VirtualFreeEx(process, remoteData, 0, MEM_RELEASE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,41 +201,32 @@ namespace Host
|
||||
|
||||
void DetachProcess(DWORD processId)
|
||||
{
|
||||
LOCK(hostMutex);
|
||||
HostCommandType buffer(HOST_COMMAND_DETACH);
|
||||
WriteFile(processRecordsByIds.at(processId)->hostPipe, &buffer, sizeof(buffer), &DUMMY, nullptr);
|
||||
processRecordsByIds->at(processId)->Send(HostCommandType(HOST_COMMAND_DETACH));
|
||||
}
|
||||
|
||||
void InsertHook(DWORD processId, HookParam hp, std::string name)
|
||||
{
|
||||
LOCK(hostMutex);
|
||||
InsertHookCmd buffer(hp, name);
|
||||
WriteFile(processRecordsByIds.at(processId)->hostPipe, &buffer, sizeof(buffer), &DUMMY, nullptr);
|
||||
processRecordsByIds->at(processId)->Send(InsertHookCmd(hp, name));
|
||||
}
|
||||
|
||||
void RemoveHook(DWORD processId, uint64_t addr)
|
||||
{
|
||||
LOCK(hostMutex);
|
||||
RemoveHookCmd buffer(addr);
|
||||
WriteFile(processRecordsByIds.at(processId)->hostPipe, &buffer, sizeof(buffer), &DUMMY, nullptr);
|
||||
processRecordsByIds->at(processId)->Send(RemoveHookCmd(addr));
|
||||
}
|
||||
|
||||
HookParam GetHookParam(DWORD processId, uint64_t addr)
|
||||
{
|
||||
LOCK(hostMutex);
|
||||
return processRecordsByIds.at(processId)->GetHook(addr).hp;
|
||||
return processRecordsByIds->at(processId)->GetHook(addr).hp;
|
||||
}
|
||||
|
||||
std::wstring GetHookName(DWORD processId, uint64_t addr)
|
||||
{
|
||||
LOCK(hostMutex);
|
||||
return Util::StringToWideString(processRecordsByIds.at(processId)->GetHook(addr).hookName).value();
|
||||
return Util::StringToWideString(processRecordsByIds->at(processId)->GetHook(addr).hookName).value();
|
||||
}
|
||||
|
||||
std::shared_ptr<TextThread> GetThread(ThreadParam tp)
|
||||
{
|
||||
LOCK(hostMutex);
|
||||
return textThreadsByParams[tp];
|
||||
return textThreadsByParams->at(tp);
|
||||
}
|
||||
|
||||
void AddConsoleOutput(std::wstring text)
|
||||
|
@ -3,12 +3,10 @@
|
||||
#include "common.h"
|
||||
#include "textthread.h"
|
||||
|
||||
typedef std::function<void(DWORD)> ProcessEventCallback;
|
||||
typedef std::function<void(std::shared_ptr<TextThread>)> ThreadEventCallback;
|
||||
|
||||
namespace Host
|
||||
{
|
||||
void Start(ProcessEventCallback onAttach, ProcessEventCallback onDetach, ThreadEventCallback onCreate, ThreadEventCallback onDestroy, TextThread::OutputCallback output);
|
||||
typedef std::function<void(DWORD)> ProcessEventCallback;
|
||||
void Start(ProcessEventCallback OnConnect, ProcessEventCallback OnDisconnect, TextThread::EventCallback OnCreate, TextThread::EventCallback OnDestroy, TextThread::OutputCallback Output);
|
||||
void Close();
|
||||
|
||||
bool InjectProcess(DWORD processId, DWORD timeout = 5000);
|
||||
|
@ -4,33 +4,37 @@
|
||||
#include "host.h"
|
||||
#include "util.h"
|
||||
|
||||
TextThread::TextThread(ThreadParam tp, HookParam hp, std::wstring name) : handle(threadCounter++), name(name), tp(tp), hp(hp) {}
|
||||
TextThread::TextThread(ThreadParam tp, HookParam hp, std::wstring name) : handle(threadCounter++), name(name), tp(tp), hp(hp)
|
||||
{
|
||||
OnCreate(this);
|
||||
}
|
||||
|
||||
TextThread::~TextThread()
|
||||
{
|
||||
SetEvent(deletionEvent);
|
||||
flushThread.join();
|
||||
CloseHandle(deletionEvent);
|
||||
OnDestroy(this);
|
||||
}
|
||||
|
||||
std::wstring TextThread::GetStorage()
|
||||
{
|
||||
LOCK(storageMutex);
|
||||
return storage;
|
||||
return storage->c_str();
|
||||
}
|
||||
|
||||
void TextThread::Start()
|
||||
{
|
||||
deletionEvent = CreateEventW(nullptr, FALSE, FALSE, NULL);
|
||||
flushThread = std::thread([&] { while (WaitForSingleObject(deletionEvent, 10) == WAIT_TIMEOUT) Flush(); });
|
||||
}
|
||||
|
||||
void TextThread::AddSentence(std::wstring sentence)
|
||||
{
|
||||
if (Output(this, sentence))
|
||||
{
|
||||
LOCK(storageMutex);
|
||||
storage += sentence;
|
||||
}
|
||||
if (Output(this, sentence)) storage->append(sentence);
|
||||
}
|
||||
|
||||
void TextThread::Push(const BYTE* data, int len)
|
||||
{
|
||||
if (len < 0) return;
|
||||
if (!flushThread.joinable() || len < 0) return;
|
||||
LOCK(bufferMutex);
|
||||
if (hp.type & USING_UNICODE) buffer += std::wstring((wchar_t*)data, len / 2);
|
||||
else if (auto converted = Util::StringToWideString(std::string((char*)data, len), hp.codepage ? hp.codepage : defaultCodepage)) buffer += converted.value();
|
||||
|
@ -6,8 +6,9 @@
|
||||
class TextThread
|
||||
{
|
||||
public:
|
||||
typedef std::function<void(TextThread*)> EventCallback;
|
||||
typedef std::function<bool(TextThread*, std::wstring&)> OutputCallback;
|
||||
|
||||
inline static EventCallback OnCreate, OnDestroy;
|
||||
inline static OutputCallback Output;
|
||||
|
||||
inline static int flushDelay = 400; // flush every 400ms by default
|
||||
@ -21,6 +22,7 @@ public:
|
||||
~TextThread();
|
||||
|
||||
std::wstring GetStorage();
|
||||
void Start();
|
||||
void AddSentence(std::wstring sentence);
|
||||
void Push(const BYTE* data, int len);
|
||||
|
||||
@ -32,13 +34,11 @@ public:
|
||||
private:
|
||||
void Flush();
|
||||
|
||||
ThreadSafePtr<std::wstring> storage;
|
||||
std::wstring buffer;
|
||||
std::unordered_set<wchar_t> repeatingChars;
|
||||
std::mutex bufferMutex;
|
||||
std::wstring storage;
|
||||
std::mutex storageMutex;
|
||||
|
||||
HANDLE deletionEvent = CreateEventW(nullptr, FALSE, FALSE, NULL);
|
||||
std::thread flushThread = std::thread([&] { while (WaitForSingleObject(deletionEvent, 10) == WAIT_TIMEOUT) Flush(); });
|
||||
DWORD lastPushTime = GetTickCount();
|
||||
AutoHandle<> deletionEvent = NULL;
|
||||
std::thread flushThread;
|
||||
DWORD lastPushTime;
|
||||
};
|
||||
|
@ -1,16 +1,36 @@
|
||||
#include "util.h"
|
||||
#include "types.h"
|
||||
#include <Psapi.h>
|
||||
|
||||
namespace Util
|
||||
{
|
||||
std::optional<std::wstring> GetModuleFileName(DWORD processId, HMODULE module)
|
||||
{
|
||||
if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
|
||||
{
|
||||
std::vector<wchar_t> buffer(MAX_PATH);
|
||||
if (GetModuleFileNameExW(process, module, buffer.data(), MAX_PATH)) return buffer.data();
|
||||
else return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<std::wstring> GetModuleFileName(HMODULE module)
|
||||
{
|
||||
std::vector<wchar_t> buffer(MAX_PATH);
|
||||
if (::GetModuleFileNameW(module, buffer.data(), MAX_PATH)) return buffer.data();
|
||||
else return {};
|
||||
}
|
||||
|
||||
std::optional<std::wstring> GetClipboardText()
|
||||
{
|
||||
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return {};
|
||||
if (!OpenClipboard(NULL)) return {};
|
||||
|
||||
if (HANDLE clipboardHandle = GetClipboardData(CF_UNICODETEXT))
|
||||
if (HANDLE clipboard = GetClipboardData(CF_UNICODETEXT))
|
||||
{
|
||||
std::wstring ret = (wchar_t*)GlobalLock(clipboardHandle);
|
||||
GlobalUnlock(clipboardHandle);
|
||||
std::wstring ret = (wchar_t*)GlobalLock(clipboard);
|
||||
GlobalUnlock(clipboard);
|
||||
CloseClipboard();
|
||||
return ret;
|
||||
}
|
||||
|
@ -4,6 +4,9 @@
|
||||
|
||||
namespace Util
|
||||
{
|
||||
struct NamedPipeHandleCloser { void operator()(void* h) { DisconnectNamedPipe(h); CloseHandle(h); } };
|
||||
std::optional<std::wstring> GetModuleFileName(DWORD processId, HMODULE module = NULL);
|
||||
std::optional<std::wstring> GetModuleFileName(HMODULE module = NULL);
|
||||
std::optional<std::wstring> GetClipboardText();
|
||||
std::optional<std::wstring> StringToWideString(std::string text, UINT encoding = CP_UTF8);
|
||||
// return true if repetition found (see https://github.com/Artikash/Textractor/issues/40)
|
||||
|
23
GUI/main.cpp
23
GUI/main.cpp
@ -1,5 +1,5 @@
|
||||
#include "mainwindow.h"
|
||||
#include "misc.h"
|
||||
#include "host/util.h"
|
||||
#include <sstream>
|
||||
#include <QApplication>
|
||||
|
||||
@ -19,17 +19,19 @@ namespace
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION info = {};
|
||||
VirtualQuery(exception->ExceptionRecord->ExceptionAddress, &info, sizeof(info));
|
||||
wchar_t moduleName[MAX_PATH] = {};
|
||||
GetModuleFileNameW((HMODULE)info.AllocationBase, moduleName, MAX_PATH);
|
||||
|
||||
std::wstringstream errorMsg;
|
||||
errorMsg << std::uppercase << std::hex <<
|
||||
L"Error code: " << exception->ExceptionRecord->ExceptionCode << std::endl <<
|
||||
L"Error address: " << (uint64_t)exception->ExceptionRecord->ExceptionAddress << std::endl <<
|
||||
L"Error in module: " << moduleName << std::endl;
|
||||
L"Error address: " << exception->ExceptionRecord->ExceptionAddress << std::endl <<
|
||||
L"Error in module: " << Util::GetModuleFileName((HMODULE)info.AllocationBase).value_or(L"Could not find") << std::endl <<
|
||||
L"Additional info: " << info.AllocationBase << std::endl;
|
||||
|
||||
if (exception->ExceptionRecord->ExceptionCode == 0xE06D7363)
|
||||
{
|
||||
errorMsg << L"Additional info: " << GetCppExceptionInfo(exception) << std::endl;
|
||||
if (errorMsg.str().find(L"exception")) errorMsg << ((std::exception*)exception->ExceptionRecord->ExceptionInformation[1])->what();
|
||||
}
|
||||
|
||||
for (int i = 0; i < exception->ExceptionRecord->NumberParameters; ++i)
|
||||
errorMsg << L"Additional info: " << exception->ExceptionRecord->ExceptionInformation[i] << std::endl;
|
||||
@ -38,7 +40,7 @@ namespace
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void Terminate()
|
||||
__declspec(noreturn) void Terminate()
|
||||
{
|
||||
MessageBoxW(NULL, lastError.c_str(), L"Textractor ERROR", MB_ICONERROR);
|
||||
std::abort();
|
||||
@ -50,9 +52,12 @@ namespace
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
AddVectoredExceptionHandler(FALSE, ExceptionLogger);
|
||||
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)Terminate);
|
||||
QString exe = GetFullModuleName(GetCurrentProcessId());
|
||||
SetCurrentDirectoryW(exe.left(exe.lastIndexOf("\\")).toStdWString().c_str());
|
||||
SetUnhandledExceptionFilter([](auto) -> LONG { Terminate(); });
|
||||
|
||||
std::wstring exe = Util::GetModuleFileName().value();
|
||||
while (exe.back() != L'\\') exe.pop_back();
|
||||
SetCurrentDirectoryW(exe.c_str());
|
||||
|
||||
QApplication a(argc, argv);
|
||||
MainWindow w;
|
||||
w.show();
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "extenwindow.h"
|
||||
#include "setdialog.h"
|
||||
#include "misc.h"
|
||||
#include <QTimer>
|
||||
#include <QInputDialog>
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) :
|
||||
@ -22,20 +23,12 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
if (settings.contains(MAX_BUFFER_SIZE)) TextThread::maxBufferSize = settings.value(MAX_BUFFER_SIZE).toInt();
|
||||
if (settings.contains(DEFAULT_CODEPAGE)) TextThread::defaultCodepage = settings.value(DEFAULT_CODEPAGE).toInt();
|
||||
|
||||
qRegisterMetaType<std::shared_ptr<TextThread>>();
|
||||
|
||||
connect(this, &MainWindow::SigAddProcess, this, &MainWindow::AddProcess);
|
||||
connect(this, &MainWindow::SigRemoveProcess, this, &MainWindow::RemoveProcess);
|
||||
connect(this, &MainWindow::SigAddThread, this, &MainWindow::AddThread);
|
||||
connect(this, &MainWindow::SigRemoveThread, this, &MainWindow::RemoveThread);
|
||||
connect(this, &MainWindow::SigThreadOutput, this, &MainWindow::ThreadOutput);
|
||||
|
||||
Host::Start(
|
||||
[&](DWORD processId) { emit SigAddProcess(processId); },
|
||||
[&](DWORD processId) { emit SigRemoveProcess(processId); },
|
||||
[&](std::shared_ptr<TextThread> thread) { emit SigAddThread(thread); },
|
||||
[&](std::shared_ptr<TextThread> thread) { emit SigRemoveThread(thread); },
|
||||
[&](TextThread* thread, std::wstring& output) { return ProcessThreadOutput(thread, output); }
|
||||
[&](DWORD processId) { ProcessConnected(processId); },
|
||||
[&](DWORD processId) { ProcessDisconnected(processId); },
|
||||
[&](TextThread* thread) { ThreadAdded(thread); },
|
||||
[&](TextThread* thread) { ThreadRemoved(thread); },
|
||||
[&](TextThread* thread, std::wstring& output) { return SentenceReceived(thread, output); }
|
||||
);
|
||||
Host::AddConsoleOutput(ABOUT);
|
||||
}
|
||||
@ -44,8 +37,8 @@ MainWindow::~MainWindow()
|
||||
{
|
||||
settings.setValue(WINDOW, geometry());
|
||||
settings.sync();
|
||||
delete ui;
|
||||
Host::Close();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent*)
|
||||
@ -53,66 +46,71 @@ void MainWindow::closeEvent(QCloseEvent*)
|
||||
QCoreApplication::quit(); // Need to do this to kill any windows that might've been made by extensions
|
||||
}
|
||||
|
||||
void MainWindow::AddProcess(unsigned processId)
|
||||
|
||||
void MainWindow::InvokeOnMainThread(std::function<void()>&& f)
|
||||
{
|
||||
QMetaObject::invokeMethod(this, f);
|
||||
}
|
||||
|
||||
void MainWindow::ProcessConnected(DWORD processId)
|
||||
{
|
||||
if (processId == 0) return;
|
||||
processCombo->addItem(QString::number(processId, 16).toUpper() + ": " + GetModuleName(processId));
|
||||
QFile file(HOOK_SAVE_FILE);
|
||||
file.open(QIODevice::ReadOnly);
|
||||
QString processName = GetFullModuleName(processId);
|
||||
QStringList allProcesses = QString(file.readAll()).split("\r", QString::SkipEmptyParts);
|
||||
for (auto hooks = allProcesses.rbegin(); hooks != allProcesses.rend(); ++hooks)
|
||||
if (hooks->contains(processName))
|
||||
InvokeOnMainThread([&, processId]
|
||||
{
|
||||
processCombo->addItem(QString::number(processId, 16).toUpper() + ": " + GetModuleName(processId));
|
||||
QString processName = GetFullModuleName(processId);
|
||||
QStringList allProcesses = QString(QAutoFile(HOOK_SAVE_FILE, QIODevice::ReadOnly)->readAll()).split("\r", QString::SkipEmptyParts);
|
||||
for (auto hooks = allProcesses.rbegin(); hooks != allProcesses.rend(); ++hooks)
|
||||
if (hooks->contains(processName))
|
||||
{
|
||||
for (auto hook : hooks->split(" , "))
|
||||
if (auto hp = ParseCode(hook)) Host::InsertHook(processId, hp.value());
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::ProcessDisconnected(DWORD processId)
|
||||
{
|
||||
InvokeOnMainThread([&, processId] { processCombo->removeItem(processCombo->findText(QString::number(processId, 16).toUpper() + ":", Qt::MatchStartsWith)); });
|
||||
}
|
||||
|
||||
void MainWindow::ThreadAdded(TextThread* thread)
|
||||
{
|
||||
QString ttString = TextThreadString(thread) + QString::fromStdWString(thread->name) + " (" + GenerateCode(thread->hp, thread->tp.processId) + ")";
|
||||
InvokeOnMainThread([&, ttString] { ttCombo->addItem(ttString); });
|
||||
}
|
||||
|
||||
void MainWindow::ThreadRemoved(TextThread* thread)
|
||||
{
|
||||
QString ttString = TextThreadString(thread);
|
||||
InvokeOnMainThread([&, ttString]
|
||||
{
|
||||
int threadIndex = ttCombo->findText(ttString, Qt::MatchStartsWith);
|
||||
if (threadIndex == ttCombo->currentIndex())
|
||||
{
|
||||
for (auto hook : hooks->split(" , "))
|
||||
if (auto hp = ParseCode(hook)) Host::InsertHook(processId, hp.value());
|
||||
return;
|
||||
ttCombo->setCurrentIndex(0);
|
||||
on_ttCombo_activated(0);
|
||||
}
|
||||
ttCombo->removeItem(threadIndex);
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::RemoveProcess(unsigned processId)
|
||||
bool MainWindow::SentenceReceived(TextThread* thread, std::wstring& sentence)
|
||||
{
|
||||
processCombo->removeItem(processCombo->findText(QString::number(processId, 16).toUpper() + ":", Qt::MatchStartsWith));
|
||||
}
|
||||
|
||||
void MainWindow::AddThread(std::shared_ptr<TextThread> thread)
|
||||
{
|
||||
ttCombo->addItem(
|
||||
TextThreadString(thread.get()) +
|
||||
QString::fromStdWString(thread->name) +
|
||||
" (" +
|
||||
GenerateCode(thread->hp, thread->tp.processId) +
|
||||
")"
|
||||
);
|
||||
}
|
||||
|
||||
void MainWindow::RemoveThread(std::shared_ptr<TextThread> thread)
|
||||
{
|
||||
int threadIndex = ttCombo->findText(TextThreadString(thread.get()), Qt::MatchStartsWith);
|
||||
if (threadIndex == ttCombo->currentIndex())
|
||||
if (DispatchSentenceToExtensions(sentence, GetMiscInfo(thread)))
|
||||
{
|
||||
ttCombo->setCurrentIndex(0);
|
||||
on_ttCombo_activated(0);
|
||||
}
|
||||
ttCombo->removeItem(threadIndex);
|
||||
}
|
||||
|
||||
void MainWindow::ThreadOutput(QString threadString, QString output)
|
||||
{
|
||||
if (ttCombo->currentText().startsWith(threadString))
|
||||
{
|
||||
textOutput->moveCursor(QTextCursor::End);
|
||||
textOutput->insertPlainText(output);
|
||||
textOutput->moveCursor(QTextCursor::End);
|
||||
}
|
||||
}
|
||||
|
||||
bool MainWindow::ProcessThreadOutput(TextThread* thread, std::wstring& output)
|
||||
{
|
||||
if (DispatchSentenceToExtensions(output, GetMiscInfo(thread)))
|
||||
{
|
||||
output += L"\r\n";
|
||||
emit SigThreadOutput(TextThreadString(thread), QString::fromStdWString(output));
|
||||
sentence += L"\r\n";
|
||||
QString ttString = TextThreadString(thread);
|
||||
InvokeOnMainThread([&, ttString, sentence]
|
||||
{
|
||||
if (ttCombo->currentText().startsWith(ttString))
|
||||
{
|
||||
textOutput->moveCursor(QTextCursor::End);
|
||||
textOutput->insertPlainText(QString::fromStdWString(sentence));
|
||||
textOutput->moveCursor(QTextCursor::End);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -130,9 +128,9 @@ QString MainWindow::TextThreadString(TextThread* thread)
|
||||
).toUpper();
|
||||
}
|
||||
|
||||
ThreadParam MainWindow::ParseTextThreadString(QString textThreadString)
|
||||
ThreadParam MainWindow::ParseTextThreadString(QString ttString)
|
||||
{
|
||||
QStringList threadParam = textThreadString.split(":");
|
||||
QStringList threadParam = ttString.split(":");
|
||||
return { threadParam[1].toUInt(nullptr, 16), threadParam[2].toULongLong(nullptr, 16), threadParam[3].toULongLong(nullptr, 16), threadParam[4].toULongLong(nullptr, 16) };
|
||||
}
|
||||
|
||||
@ -214,14 +212,10 @@ void MainWindow::on_unhookButton_clicked()
|
||||
|
||||
void MainWindow::on_saveButton_clicked()
|
||||
{
|
||||
auto hooks = GetAllHooks(GetSelectedProcessId());
|
||||
QString hookList = GetFullModuleName(GetSelectedProcessId());
|
||||
for (auto hp : hooks)
|
||||
if (!(hp.type & HOOK_ENGINE))
|
||||
hookList += " , " + GenerateCode(hp, GetSelectedProcessId());
|
||||
QFile file(HOOK_SAVE_FILE);
|
||||
file.open(QIODevice::Append);
|
||||
file.write((hookList + "\r\n").toUtf8());
|
||||
for (auto hp : GetAllHooks(GetSelectedProcessId()))
|
||||
if (!(hp.type & HOOK_ENGINE)) hookList += " , " + GenerateCode(hp, GetSelectedProcessId());
|
||||
QAutoFile(HOOK_SAVE_FILE, QIODevice::Append)->write((hookList + "\r\n").toUtf8());
|
||||
}
|
||||
|
||||
void MainWindow::on_setButton_clicked()
|
||||
|
@ -13,8 +13,6 @@ namespace Ui
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(std::shared_ptr<TextThread>);
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -23,19 +21,7 @@ public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
signals:
|
||||
void SigAddProcess(unsigned processId);
|
||||
void SigRemoveProcess(unsigned processId);
|
||||
void SigAddThread(std::shared_ptr<TextThread>);
|
||||
void SigRemoveThread(std::shared_ptr<TextThread>);
|
||||
void SigThreadOutput(QString threadString, QString output);
|
||||
|
||||
private slots:
|
||||
void AddProcess(unsigned processId);
|
||||
void RemoveProcess(unsigned processId);
|
||||
void AddThread(std::shared_ptr<TextThread> thread);
|
||||
void RemoveThread(std::shared_ptr<TextThread> thread);
|
||||
void ThreadOutput(QString threadString, QString output); // this function doesn't take TextThread* because it might be destroyed on pipe thread
|
||||
void on_attachButton_clicked();
|
||||
void on_detachButton_clicked();
|
||||
void on_unhookButton_clicked();
|
||||
@ -46,9 +32,14 @@ private slots:
|
||||
void on_ttCombo_activated(int index);
|
||||
|
||||
private:
|
||||
bool ProcessThreadOutput(TextThread* thread, std::wstring& output);
|
||||
void InvokeOnMainThread(std::function<void()>&& f);
|
||||
void ProcessConnected(DWORD processId);
|
||||
void ProcessDisconnected(DWORD processId);
|
||||
void ThreadAdded(TextThread* thread);
|
||||
void ThreadRemoved(TextThread* thread);
|
||||
bool SentenceReceived(TextThread* thread, std::wstring& sentence);
|
||||
QString TextThreadString(TextThread* thread);
|
||||
ThreadParam ParseTextThreadString(QString textThreadString);
|
||||
ThreadParam ParseTextThreadString(QString ttString);
|
||||
DWORD GetSelectedProcessId();
|
||||
std::unordered_map<std::string, int64_t> GetMiscInfo(TextThread* thread);
|
||||
QVector<HookParam> GetAllHooks(DWORD processId);
|
||||
|
@ -4,6 +4,15 @@
|
||||
#include "qtcommon.h"
|
||||
#include "types.h"
|
||||
|
||||
class QAutoFile
|
||||
{
|
||||
public:
|
||||
QAutoFile(QString name, QIODevice::OpenMode mode) : f(name) { f.open(mode); }
|
||||
QFile* operator->() { return &f; }
|
||||
private:
|
||||
QFile f;
|
||||
};
|
||||
|
||||
QString GetFullModuleName(DWORD processId, HMODULE module = NULL);
|
||||
QString GetModuleName(DWORD processId, HMODULE module = NULL);
|
||||
QMultiHash<QString, DWORD> GetAllProcesses();
|
||||
|
Loading…
Reference in New Issue
Block a user