bunch of refactoring and cleanup

This commit is contained in:
Akash Mozumdar 2019-01-09 22:35:01 -05:00
parent 84e9beea63
commit 0afdafb3d1
13 changed files with 66 additions and 56 deletions

View File

@ -21,7 +21,11 @@ namespace
LONG WINAPI ExceptionLogger(EXCEPTION_POINTERS* exception)
{
thread_local static auto _ = std::invoke(std::set_terminate, Terminate);
thread_local static auto _ = std::invoke([]
{
std::set_terminate(Terminate);
return 0;
});
MEMORY_BASIC_INFORMATION info = {};
VirtualQuery(exception->ExceptionRecord->ExceptionAddress, &info, sizeof(info));
@ -46,6 +50,7 @@ namespace
auto _ = std::invoke([]
{
AddVectoredExceptionHandler(FALSE, ExceptionLogger);
return SetUnhandledExceptionFilter([](auto) -> LONG { Terminate(); });
SetUnhandledExceptionFilter([](auto) -> LONG { Terminate(); });
return 0;
});
}

View File

@ -35,21 +35,21 @@ namespace
if (!module) return;
FARPROC callback = GetProcAddress(module, "OnNewSentence");
if (!callback) return;
LOCK(extenMutex);
std::scoped_lock writeLock(extenMutex);
extensions[extenName] = (wchar_t*(*)(const wchar_t*, const InfoForExtension*))callback;
extenNames.push_back(extenName);
}
void Unload(QString extenName)
{
LOCK(extenMutex);
std::scoped_lock writeLock(extenMutex);
extenNames.erase(std::remove(extenNames.begin(), extenNames.end(), extenName), extenNames.end());
FreeLibrary(GetModuleHandleW(S(extenName).c_str()));
}
void Reorder(QStringList extenNames)
{
LOCK(extenMutex);
std::scoped_lock writeLock(extenMutex);
::extenNames = extenNames;
}
}
@ -63,7 +63,7 @@ bool DispatchSentenceToExtensions(std::wstring& sentence, std::unordered_map<con
InfoForExtension* miscInfoTraverser = &miscInfoLinkedList;
for (auto[name, value] : miscInfo) miscInfoTraverser = miscInfoTraverser->next = new InfoForExtension{ name, value, nullptr };
std::shared_lock sharedLock(extenMutex);
std::shared_lock readLock(extenMutex);
for (auto extenName : extenNames)
{
wchar_t* nextBuffer = extensions[extenName](sentenceBuffer, &miscInfoLinkedList);
@ -100,7 +100,7 @@ void ExtenWindow::Sync()
{
ui->extenList->clear();
QAutoFile extenSaveFile(EXTEN_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate);
std::shared_lock sharedLock(extenMutex);
std::shared_lock readLock(extenMutex);
for (auto extenName : extenNames)
{
ui->extenList->addItem(extenName);

View File

@ -31,7 +31,7 @@ namespace
TextHook GetHook(uint64_t addr)
{
if (view == nullptr) return {};
LOCK(viewMutex);
std::scoped_lock lock(viewMutex);
auto hooks = (const TextHook*)view;
for (int i = 0; i < MAX_HOOK; ++i)
if (hooks[i].address == addr) return hooks[i];
@ -53,14 +53,17 @@ namespace
WinMutex viewMutex;
};
ThreadSafe<std::unordered_map<ThreadParam, std::unique_ptr<TextThread>>, std::recursive_mutex> textThreadsByParams;
size_t HashThreadParam(ThreadParam tp)
{
std::hash<int64_t> hash;
return hash(hash(tp.processId) + hash(tp.addr) + hash(tp.ctx) + hash(tp.ctx2));
}
ThreadSafe<std::unordered_map<ThreadParam, std::unique_ptr<TextThread>, Functor<HashThreadParam>>, std::recursive_mutex> textThreadsByParams;
ThreadSafe<std::unordered_map<DWORD, ProcessRecord>, std::recursive_mutex> processRecordsByIds;
ThreadParam CONSOLE{ 0, -1ULL, -1ULL, -1ULL }, CLIPBOARD{ 0, 0, -1ULL, -1ULL };
void RemoveThreads(std::function<bool(ThreadParam)> removeIf)
{
std::vector<std::unique_ptr<TextThread>> removedThreads;
std::vector<std::unique_ptr<TextThread>> removedThreads; // delay destruction until after lock is released
auto[lock, textThreadsByParams] = ::textThreadsByParams.operator->();
for (auto it = textThreadsByParams->begin(); it != textThreadsByParams->end(); removeIf(it->first) ? it = textThreadsByParams->erase(it) : ++it)
if (removeIf(it->first)) removedThreads.emplace_back(std::move(it->second));
@ -73,10 +76,11 @@ namespace
SECURITY_DESCRIPTOR pipeSD = {};
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 };
SECURITY_ATTRIBUTES pipeSA = { sizeof(pipeSA), &pipeSD, FALSE };
struct NamedPipeHandleCloser { void operator()(void* h) { DisconnectNamedPipe(h); CloseHandle(h); } };
AutoHandle<NamedPipeHandleCloser>
struct PipeCloser { void operator()(HANDLE h) { DisconnectNamedPipe(h); CloseHandle(h); } };
AutoHandle<PipeCloser>
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);
@ -84,7 +88,7 @@ namespace
BYTE buffer[PIPE_BUFFER_SIZE] = {};
DWORD bytesRead, processId;
ReadFile(hookPipe, &processId, sizeof(processId), &bytesRead, nullptr);
processRecordsByIds->try_emplace(processId, processId, hostPipe);
processRecordsByIds->emplace(processId, processId, hostPipe);
CreatePipe();
@ -122,7 +126,7 @@ namespace
SetWindowsHookExW(WH_GETMESSAGE, [](int statusCode, WPARAM wParam, LPARAM lParam)
{
if (statusCode == HC_ACTION && wParam == PM_REMOVE && ((MSG*)lParam)->message == WM_CLIPBOARDUPDATE)
if (auto text = Util::GetClipboardText()) Host::GetThread(CLIPBOARD)->AddSentence(text.value());
if (auto text = Util::GetClipboardText()) Host::GetThread(Host::clipboard)->AddSentence(text.value());
return CallNextHookEx(NULL, statusCode, wParam, lParam);
}, NULL, GetCurrentThreadId());
}
@ -137,9 +141,9 @@ namespace Host
TextThread::OnCreate = OnCreate;
TextThread::OnDestroy = OnDestroy;
TextThread::Output = Output;
processRecordsByIds->try_emplace(CONSOLE.processId, CONSOLE.processId, INVALID_HANDLE_VALUE);
textThreadsByParams->insert({ CONSOLE, std::make_unique<TextThread>(CONSOLE, HookParam{}, L"Console") });
textThreadsByParams->insert({ CLIPBOARD, std::make_unique<TextThread>(CLIPBOARD, HookParam{}, L"Clipboard") });
processRecordsByIds->emplace(console.processId, console.processId, INVALID_HANDLE_VALUE);
textThreadsByParams->insert({ console, std::make_unique<TextThread>(console, HookParam{}, CONSOLE) });
textThreadsByParams->insert({ Host::clipboard, std::make_unique<TextThread>(Host::clipboard, HookParam{}, CLIPBOARD) });
StartCapturingClipboard();
CreatePipe();
}
@ -169,7 +173,7 @@ namespace Host
return false;
}
#endif
if (LPVOID remoteData = VirtualAllocEx(process, nullptr, location.size() * 2 + 2, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))
if (LPVOID remoteData = VirtualAllocEx(process, nullptr, (location.size() + 1) * sizeof(wchar_t), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))
{
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))
@ -208,6 +212,6 @@ namespace Host
void AddConsoleOutput(std::wstring text)
{
GetThread(CONSOLE)->AddSentence(text);
GetThread(console)->AddSentence(text);
}
}

View File

@ -18,4 +18,6 @@ namespace Host
void AddConsoleOutput(std::wstring text);
inline int defaultCodepage = SHIFT_JIS;
constexpr ThreadParam console{ 0, -1LL, -1LL, -1LL }, clipboard{ 0, 0, -1LL, -1LL };
}

View File

@ -27,7 +27,7 @@ void TextThread::AddSentence(const std::wstring& sentence)
void TextThread::Push(const BYTE* data, int len)
{
if (len < 0) return;
LOCK(bufferMutex);
std::scoped_lock 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 : Host::defaultCodepage)) buffer += converted.value();
else Host::AddConsoleOutput(INVALID_CODEPAGE);
@ -49,7 +49,7 @@ void TextThread::Flush()
for (auto sentence : sentences)
if (Output(this, sentence)) storage->append(sentence);
LOCK(bufferMutex);
std::scoped_lock lock(bufferMutex);
if (buffer.empty()) return;
if (buffer.size() < maxBufferSize && GetTickCount() - lastPushTime < flushDelay) return;
AddSentence(buffer);

View File

@ -31,11 +31,11 @@ private:
void Flush();
struct TimerDeleter { void operator()(void* h) { DeleteTimerQueueTimer(NULL, h, INVALID_HANDLE_VALUE); } };
std::wstring buffer;
std::unordered_set<wchar_t> repeatingChars;
std::mutex bufferMutex;
DWORD lastPushTime;
ThreadSafe<std::vector<std::wstring>> queuedSentences;
struct TimerDeleter { void operator()(HANDLE h) { DeleteTimerQueueTimer(NULL, h, INVALID_HANDLE_VALUE); } };
AutoHandle<TimerDeleter> timer = NULL; // this needs to be last so it's destructed first
};

View File

@ -10,7 +10,7 @@ namespace Util
{
std::vector<wchar_t> buffer(MAX_PATH);
if (GetModuleFileNameExW(process, module, buffer.data(), MAX_PATH)) return buffer.data();
else return {};
return {};
}
return {};
}
@ -18,8 +18,8 @@ namespace Util
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 {};
if (GetModuleFileNameW(module, buffer.data(), MAX_PATH)) return buffer.data();
return {};
}
std::optional<std::wstring> GetClipboardText()
@ -27,22 +27,17 @@ namespace Util
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return {};
if (!OpenClipboard(NULL)) return {};
if (HANDLE clipboard = GetClipboardData(CF_UNICODETEXT))
{
std::wstring ret = (wchar_t*)GlobalLock(clipboard);
GlobalUnlock(clipboard);
CloseClipboard();
return ret;
}
std::optional<std::wstring> ret;
if (AutoHandle<Functor<GlobalUnlock>> clipboard = GetClipboardData(CF_UNICODETEXT)) ret = (wchar_t*)GlobalLock(clipboard);
CloseClipboard();
return {};
return ret;
}
std::optional<std::wstring> StringToWideString(std::string text, UINT encoding)
{
std::vector<wchar_t> buffer(text.size() + 1);
if (MultiByteToWideChar(encoding, 0, text.c_str(), -1, buffer.data(), buffer.size())) return buffer.data();
else return {};
return {};
}
bool RemoveRepetition(std::wstring& text)

View File

@ -53,11 +53,11 @@ MainWindow::MainWindow(QWidget *parent) :
std::thread([]
{
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
// Queries GitHub releases API https://developer.github.com/v3/repos/releases/ and checks the last release tag to check if it's the same
struct InternetHandleCloser { void operator()(void* h) { WinHttpCloseHandle(h); } };
if (AutoHandle<InternetHandleCloser> internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0))
if (AutoHandle<InternetHandleCloser> connection = WinHttpConnect(internet, L"api.github.com", INTERNET_DEFAULT_HTTPS_PORT, 0))
if (AutoHandle<InternetHandleCloser> request = WinHttpOpenRequest(connection, L"GET", L"/repos/Artikash/Textractor/releases", NULL, NULL, NULL, WINHTTP_FLAG_SECURE))
if (InternetHandle internet = WinHttpOpen(L"Mozilla/5.0 Textractor", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0))
if (InternetHandle connection = WinHttpConnect(internet, L"api.github.com", INTERNET_DEFAULT_HTTPS_PORT, 0))
if (InternetHandle request = WinHttpOpenRequest(connection, L"GET", L"/repos/Artikash/Textractor/releases", NULL, NULL, NULL, WINHTTP_FLAG_SECURE))
if (WinHttpSendRequest(request, NULL, 0, NULL, 0, 0, NULL))
{
DWORD bytesRead;

View File

@ -24,7 +24,7 @@ enum HostNotificationType
HOST_NOTIFICATION_RMVHOOK = 2
};
enum HookParamType : unsigned long
enum HookParamType : unsigned
{
USING_STRING = 0x1, // type(data) is char* or wchar_t* and has length
USING_UNICODE = 0x2, // type(data) is wchar_t or wchar_t*

View File

@ -22,7 +22,7 @@ u8"OR\r\n"
u8"Enter hook code\r\n"
u8"H{A|B|W|S|Q|V}[N][codepage#]data_offset[*deref_offset1][:split_offset[*deref_offset2]]@addr[:module[:func]]\r\n"
u8"All numbers except codepage in hexadecimal\r\n"
u8"Default codepage is 932 (Shift-JIS) but this can be changed in settings"
u8"Default codepage is 932 (Shift-JIS) but this can be changed in settings\r\n"
u8"A/B: codepage char little/big endian\r\n"
u8"W: UTF-16 char\r\n"
u8"S/Q/V: codepage/UTF-16/UTF-8 string\r\n"
@ -37,6 +37,8 @@ constexpr auto WINDOW = u8"Window";
constexpr auto DEFAULT_CODEPAGE = u8"Default Codepage";
constexpr auto FLUSH_DELAY = u8"Flush Delay";
constexpr auto MAX_BUFFER_SIZE = u8"Max Buffer Size";
constexpr auto CONSOLE = L"Console";
constexpr auto CLIPBOARD = L"Clipboard";
constexpr auto ABOUT = L"Textractor beta v" CURRENT_VERSION L" (project homepage: https://github.com/Artikash/Textractor)\r\n"
L"Made by me: Artikash (email: akashmozumdar@gmail.com)\r\n"
L"Please contact me with any problems, feature requests, or questions relating to Textractor\r\n"

View File

@ -5,6 +5,13 @@
template <typename T> using Array = T[];
template <auto F>
struct Functor
{
template <typename... Args>
auto operator()(Args&&... args) const { return std::invoke(F, std::forward<Args>(args)...); }
};
template<typename E, typename M = std::mutex>
class ThreadSafe
{
@ -26,8 +33,7 @@ private:
M mtx;
};
struct DefHandleCloser { void operator()(void* h) { CloseHandle(h); } };
template <typename HandleCloser = DefHandleCloser>
template <typename HandleCloser = Functor<CloseHandle>>
class AutoHandle
{
public:
@ -74,16 +80,14 @@ struct HookParam
struct ThreadParam
{
bool operator==(ThreadParam other) const { return processId == other.processId && addr == other.addr && ctx == other.ctx && ctx2 == other.ctx2; }
DWORD processId;
uint64_t addr;
uint64_t ctx; // The context of the hook: by default the first value on stack, usually the return address
uint64_t ctx2; // The subcontext of the hook: 0 by default, generated in a method specific to the hook
};
// Artikash 5/31/2018: required for unordered_map to work with struct key
template <> struct std::hash<ThreadParam> { size_t operator()(ThreadParam tp) const { return std::hash<int64_t>()((tp.processId + tp.addr) ^ (tp.ctx + tp.ctx2)); } };
static bool operator==(ThreadParam one, ThreadParam two) { return one.processId == two.processId && one.addr == two.addr && one.ctx == two.ctx && one.ctx2 == two.ctx2; }
class WinMutex // Like CMutex but works with lock_guard
class WinMutex // Like CMutex but works with scoped_lock
{
public:
WinMutex(std::wstring name) : m(CreateMutexW(nullptr, FALSE, name.c_str())) {}
@ -114,5 +118,3 @@ struct HookRemovedNotif // From hook
int command = HOST_NOTIFICATION_RMVHOOK;
uint64_t address;
};
#define LOCK(mutex) std::lock_guard lock(mutex)

View File

@ -78,11 +78,11 @@ DWORD WINAPI Pipe(LPVOID)
void TextOutput(ThreadParam tp, BYTE* text, int len)
{
if (len < 0) return;
if (len > PIPE_BUFFER_SIZE - sizeof(ThreadParam)) len = PIPE_BUFFER_SIZE - sizeof(ThreadParam);
if (len > PIPE_BUFFER_SIZE - sizeof(tp)) len = PIPE_BUFFER_SIZE - sizeof(tp);
BYTE buffer[PIPE_BUFFER_SIZE] = {};
*(ThreadParam*)buffer = tp;
memcpy(buffer + sizeof(ThreadParam), text, len);
WriteFile(hookPipe, buffer, sizeof(ThreadParam) + len, &DUMMY, nullptr);
memcpy(buffer + sizeof(tp), text, len);
WriteFile(hookPipe, buffer, sizeof(tp) + len, &DUMMY, nullptr);
}
void ConsoleOutput(LPCSTR text, ...)

View File

@ -100,7 +100,7 @@ void SetTrigger()
bool TextHook::Insert(HookParam h, DWORD set_flag)
{
LOCK(*viewMutex);
std::scoped_lock lock(*viewMutex);
hp = h;
address = hp.address;
hp.type |= set_flag;
@ -280,7 +280,7 @@ void TextHook::RemoveReadCode()
void TextHook::Clear()
{
LOCK(*viewMutex);
std::scoped_lock lock(*viewMutex);
ConsoleOutput(REMOVING_HOOK, hp.name);
if (hp.type & DIRECT_READ) RemoveReadCode();
else RemoveHookCode();