add auto attaching and forgetting processes. make settings text consistent. other refactors

This commit is contained in:
Akash Mozumdar 2019-06-27 12:39:44 +05:30
parent 50af685d96
commit f87da8aedf
6 changed files with 102 additions and 55 deletions

View File

@ -281,13 +281,14 @@ namespace Util
return {};
}
std::vector<DWORD> GetAllProcessIds()
std::vector<std::pair<DWORD, std::optional<std::wstring>>> GetAllProcesses()
{
std::vector<DWORD> processIds(10000);
DWORD spaceUsed = 0;
EnumProcesses(processIds.data(), 10000 * sizeof(DWORD), &spaceUsed);
processIds.resize(spaceUsed / sizeof(DWORD));
return processIds;
std::vector<std::pair<DWORD, std::optional<std::wstring>>> processes;
for (int i = 0; i < spaceUsed / sizeof(DWORD); ++i) processes.push_back({ processIds[i], Util::GetModuleFilename(processIds[i]) });
return processes;
}
std::optional<std::wstring> GetClipboardText()

View File

@ -7,7 +7,7 @@ namespace Util
{
std::optional<std::wstring> GetModuleFilename(DWORD processId, HMODULE module = NULL);
std::optional<std::wstring> GetModuleFilename(HMODULE module = NULL);
std::vector<DWORD> GetAllProcessIds();
std::vector<std::pair<DWORD, std::optional<std::wstring>>> GetAllProcesses();
std::optional<std::wstring> GetClipboardText();
std::optional<std::wstring> StringToWideString(const std::string& text, UINT encoding = CP_UTF8);
// return true if repetition found (see https://github.com/Artikash/Textractor/issues/40)

View File

@ -1,10 +1,30 @@
#include "mainwindow.h"
#include "host/util.h"
#include <winhttp.h>
#include <QApplication>
extern const wchar_t* UPDATE_AVAILABLE;
int main(int argc, char *argv[])
{
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
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))
{
char buffer[1000] = {};
WinHttpReceiveResponse(request, NULL);
WinHttpReadData(request, buffer, 1000, DUMMY);
if (abs(strstr(buffer, "/tag/") - strstr(buffer, VERSION)) > 10) MESSAGE(UPDATE_AVAILABLE);
}
}).detach();
QDir::setCurrent(QFileInfo(S(Util::GetModuleFilename().value())).absolutePath());
return QApplication(argc, argv), MainWindow().show(), QApplication::exec();
QApplication app(argc, argv);
return MainWindow().show(), app.exec();
}

View File

@ -1,10 +1,8 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "defs.h"
#include "extenwindow.h"
#include "host/util.h"
#include <shellapi.h>
#include <winhttp.h>
#include <QFormLayout>
#include <QLabel>
#include <QPushButton>
@ -19,6 +17,7 @@
extern const char* ATTACH;
extern const char* LAUNCH;
extern const char* DETACH;
extern const char* FORGET;
extern const char* ADD_HOOK;
extern const char* REMOVE_HOOKS;
extern const char* SAVE_HOOKS;
@ -46,12 +45,13 @@ extern const char* DOUBLE_CLICK_TO_REMOVE_HOOK;
extern const char* SAVE_SETTINGS;
extern const char* USE_JP_LOCALE;
extern const char* FILTER_REPETITION;
extern const char* AUTO_ATTACH;
extern const char* ATTACH_SAVED_ONLY;
extern const char* DEFAULT_CODEPAGE;
extern const char* FLUSH_DELAY;
extern const char* MAX_BUFFER_SIZE;
extern const wchar_t* ABOUT;
extern const wchar_t* CL_OPTIONS;
extern const wchar_t* UPDATE_AVAILABLE;
extern const wchar_t* LAUNCH_FAILED;
extern const wchar_t* INVALID_CODE;
@ -65,6 +65,7 @@ MainWindow::MainWindow(QWidget *parent) :
{ ATTACH, &MainWindow::AttachProcess },
{ LAUNCH, &MainWindow::LaunchProcess },
{ DETACH, &MainWindow::DetachProcess },
{ FORGET, &MainWindow::ForgetProcess },
{ ADD_HOOK, &MainWindow::AddHook },
{ REMOVE_HOOKS, &MainWindow::RemoveHooks },
{ SAVE_HOOKS, &MainWindow::SaveHooks },
@ -86,6 +87,8 @@ MainWindow::MainWindow(QWidget *parent) :
QSettings settings(CONFIG_FILE, QSettings::IniFormat);
if (settings.contains(WINDOW)) setGeometry(settings.value(WINDOW).toRect());
TextThread::filterRepetition = settings.value(FILTER_REPETITION, TextThread::filterRepetition).toBool();
autoAttach = settings.value(AUTO_ATTACH, autoAttach).toBool();
autoAttachSavedOnly = settings.value(ATTACH_SAVED_ONLY, autoAttachSavedOnly).toBool();
TextThread::flushDelay = settings.value(FLUSH_DELAY, TextThread::flushDelay).toInt();
TextThread::maxBufferSize = settings.value(MAX_BUFFER_SIZE, TextThread::maxBufferSize).toInt();
Host::defaultCodepage = settings.value(DEFAULT_CODEPAGE, Host::defaultCodepage).toInt();
@ -102,32 +105,32 @@ MainWindow::MainWindow(QWidget *parent) :
AttachConsole(ATTACH_PARENT_PROCESS);
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), CL_OPTIONS, wcslen(CL_OPTIONS), DUMMY, NULL);
std::vector<DWORD> processIds = Util::GetAllProcessIds();
std::vector<std::wstring> processNames;
for (auto processId : processIds) processNames.emplace_back(Util::GetModuleFilename(processId).value_or(L""));
auto processes = Util::GetAllProcesses();
int argc;
std::unique_ptr<LPWSTR[], Functor<LocalFree>> argv(CommandLineToArgvW(GetCommandLineW(), &argc));
for (int i = 0; i < argc; ++i)
if (std::wstring arg = argv[i]; arg[0] == L'/' || arg[0] == L'-')
if (arg[1] == L'p' || arg[1] == L'P')
if (DWORD processId = _wtoi(arg.substr(2).c_str())) Host::InjectProcess(processId);
else for (int i = 0; i < processIds.size(); ++i)
if (processNames[i].find(L"\\" + arg.substr(2)) != std::wstring::npos) Host::InjectProcess(processIds[i]);
else for (auto [processId, processName] : processes)
if (processName.value_or(L"").find(L"\\" + arg.substr(2)) != std::wstring::npos) Host::InjectProcess(processId);
std::thread([]
std::thread([this]
{
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
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))
{
char buffer[1000] = {};
WinHttpReceiveResponse(request, NULL);
WinHttpReadData(request, buffer, 1000, DUMMY);
if (abs(strstr(buffer, "/tag/") - strstr(buffer, VERSION)) > 10) MESSAGE(UPDATE_AVAILABLE);
}
for (; ; Sleep(10000))
{
std::unordered_set<std::wstring> attachTargets;
if (autoAttach)
for (auto process : QString(QTextFile(GAME_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts))
attachTargets.insert(S(process));
if (autoAttachSavedOnly)
for (auto process : QString(QTextFile(HOOK_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts))
attachTargets.insert(S(process.split(" , ")[0]));
if (!attachTargets.empty())
for (auto [processId, processName] : Util::GetAllProcesses())
if (processName && attachTargets.count(processName.value()) > 0 && alreadyAttached.count(processId) == 0) Host::InjectProcess(processId);
}
}).detach();
}
@ -146,7 +149,8 @@ void MainWindow::closeEvent(QCloseEvent*)
void MainWindow::ProcessConnected(DWORD processId)
{
if (processId == 0) return;
alreadyAttached.insert(processId);
QString process = S(Util::GetModuleFilename(processId).value_or(L"???"));
QMetaObject::invokeMethod(this, [this, process, processId]
{
@ -163,7 +167,7 @@ void MainWindow::ProcessConnected(DWORD processId)
if (hookList != allProcesses.rend())
for (auto hookInfo : hookList->split(" , "))
if (auto hp = Util::ParseCode(S(hookInfo))) Host::InsertHook(processId, hp.value());
else swscanf_s(S(hookInfo).c_str(), L"|%I64d:%I64d:%[^\n]", &savedThreadCtx.first, &savedThreadCtx.second, savedThreadCode, (unsigned)std::size(savedThreadCode));
else swscanf_s(S(hookInfo).c_str(), L"|%I64d:%I64d:%[^\n]", &savedThreadCtx, &savedThreadCtx2, savedThreadCode, (unsigned)std::size(savedThreadCode));
}
void MainWindow::ProcessDisconnected(DWORD processId)
@ -178,8 +182,8 @@ void MainWindow::ThreadAdded(TextThread& thread)
{
std::wstring threadCode = Util::GenerateCode(thread.hp, thread.tp.processId);
QString ttString = TextThreadString(thread) + S(thread.name) + " (" + S(threadCode) + ")";
bool savedMatch = savedThreadCtx == std::pair(thread.tp.ctx, thread.tp.ctx2) && savedThreadCode == threadCode;
if (savedMatch) savedThreadCtx.first = savedThreadCtx.second = savedThreadCode[0] = 0;
bool savedMatch = savedThreadCtx == thread.tp.ctx && savedThreadCtx2 == thread.tp.ctx2 && savedThreadCode == threadCode;
if (savedMatch) savedThreadCtx = savedThreadCtx2 = savedThreadCode[0] = 0;
QMetaObject::invokeMethod(this, [this, ttString, savedMatch]
{
ui->ttCombo->addItem(ttString);
@ -259,8 +263,8 @@ std::array<InfoForExtension, 10> MainWindow::GetMiscInfo(TextThread& thread)
void MainWindow::AttachProcess()
{
QMultiHash<QString, DWORD> allProcesses;
for (auto processId : Util::GetAllProcessIds())
if (auto processName = Util::GetModuleFilename(processId)) allProcesses.insert(QFileInfo(S(processName.value())).fileName(), processId);
for (auto [processId, processName] : Util::GetAllProcesses())
if (processName) allProcesses.insert(QFileInfo(S(processName.value())).fileName(), processId);
QStringList processList(allProcesses.uniqueKeys());
processList.sort(Qt::CaseInsensitive);
@ -319,6 +323,20 @@ void MainWindow::DetachProcess()
try { Host::DetachProcess(GetSelectedProcessId()); } catch (std::out_of_range) {}
}
void MainWindow::ForgetProcess()
{
if (auto processName = Util::GetModuleFilename(GetSelectedProcessId()))
{
for (auto file : { GAME_SAVE_FILE, HOOK_SAVE_FILE })
{
QStringList lines = QString::fromUtf8(QTextFile(file, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts);
lines.erase(std::remove_if(lines.begin(), lines.end(), [&](auto line) { return line.contains(S(processName.value())); }), lines.end());
QTextFile(file, QIODevice::WriteOnly | QIODevice::Truncate).write(lines.join("\n").toUtf8());
}
}
DetachProcess();
}
void MainWindow::AddHook()
{
if (QString hookCode = QInputDialog::getText(this, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, "", &ok, Qt::WindowCloseButtonHint); ok)
@ -369,8 +387,8 @@ void MainWindow::SaveHooks()
}
}
auto hookInfo = QStringList() << S(processName.value()) << hookCodes.values();
ThreadParam tp = current.load()->tp;
if (tp.processId == GetSelectedProcessId()) hookInfo << QString("|%1:%2:%3").arg(tp.ctx).arg(tp.ctx2).arg(S(Util::GenerateCode(Host::GetThread(tp).hp, tp.processId)));
ThreadParam tp = current->tp;
if (tp.processId == GetSelectedProcessId()) hookInfo << QString("|%1:%2:%3").arg(tp.ctx).arg(tp.ctx2).arg(S(Util::GenerateCode(current->hp, tp.processId)));
QTextFile(HOOK_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Append).write((hookInfo.join(" , ") + "\n").toUtf8());
}
@ -436,7 +454,7 @@ void MainWindow::FindHooks()
if (!dialog.exec()) return;
QByteArray pattern = QByteArray::fromHex(patternInput.text().replace("??", QString::number(XX, 16)).toUtf8());
memcpy(sp.pattern, pattern.data(), sp.length = min(pattern.size(), 25));
try { filter = std::wregex(S(filterInput.text())); } catch (std::regex_error) {};
try { filter = std::wregex(S(filterInput.text())); } catch (std::regex_error) {}
}
else
{
@ -469,28 +487,30 @@ void MainWindow::Settings()
QSettings settings(CONFIG_FILE, QSettings::IniFormat, &dialog);
QFormLayout layout(&dialog);
QPushButton saveButton(SAVE_SETTINGS, &dialog);
layout.addWidget(&saveButton);
for (auto [value, label] : Array<std::tuple<bool&, const char*>>{
{ TextThread::filterRepetition, FILTER_REPETITION },
{ autoAttach, AUTO_ATTACH },
{ autoAttachSavedOnly, ATTACH_SAVED_ONLY },
})
{
auto checkBox = new QCheckBox(&dialog);
checkBox->setChecked(value);
layout.addRow(label, checkBox);
connect(&saveButton, &QPushButton::clicked, [checkBox, label, &settings, &value] { settings.setValue(label, value = checkBox->isChecked()); });
}
for (auto [value, label] : Array<std::tuple<int&, const char*>>{
{ Host::defaultCodepage, DEFAULT_CODEPAGE },
{ TextThread::maxBufferSize, MAX_BUFFER_SIZE },
{ TextThread::flushDelay, FLUSH_DELAY },
{ Host::defaultCodepage, DEFAULT_CODEPAGE },
})
{
auto spinBox = new QSpinBox(&dialog);
spinBox->setMaximum(INT_MAX);
spinBox->setValue(value);
layout.insertRow(0, label, spinBox);
layout.addRow(label, spinBox);
connect(&saveButton, &QPushButton::clicked, [spinBox, label, &settings, &value] { settings.setValue(label, value = spinBox->value()); });
}
for (auto [value, label] : Array<std::tuple<bool&, const char*>>{
{ TextThread::filterRepetition, FILTER_REPETITION },
})
{
auto checkBox = new QCheckBox(&dialog);
checkBox->setChecked(value);
layout.insertRow(0, label, checkBox);
connect(&saveButton, &QPushButton::clicked, [checkBox, label, &settings, &value] { settings.setValue(label, value = checkBox->isChecked()); });
}
layout.addWidget(&saveButton);
connect(&saveButton, &QPushButton::clicked, &dialog, &QDialog::accept);
dialog.setWindowTitle(SETTINGS);
dialog.exec();

View File

@ -35,6 +35,7 @@ private:
void AttachProcess();
void LaunchProcess();
void DetachProcess();
void ForgetProcess();
void AddHook();
void RemoveHooks();
void SaveHooks();
@ -44,8 +45,10 @@ private:
void ViewThread(int index);
Ui::MainWindow* ui;
QWidget* extenWindow;
std::pair<uint64_t, uint64_t> savedThreadCtx;
wchar_t savedThreadCode[1000];
std::atomic<TextThread*> current;
ExtenWindow* extenWindow;
std::unordered_set<DWORD> alreadyAttached;
bool autoAttach = false, autoAttachSavedOnly = true;
uint64_t savedThreadCtx = 0, savedThreadCtx2 = 0;
wchar_t savedThreadCode[1000] = {};
TextThread* current = nullptr;
};

View File

@ -9,6 +9,7 @@
const char* ATTACH = u8"Attach to game";
const char* LAUNCH = u8"Launch game";
const char* DETACH = u8"Detach from game";
const char* FORGET = u8"Forget game";
const char* ADD_HOOK = u8"Add hook";
const char* REMOVE_HOOKS = u8"Remove hook(s)";
const char* SAVE_HOOKS = u8"Save hook(s)";
@ -63,10 +64,12 @@ 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";
const char* MAX_BUFFER_SIZE = u8"Max Buffer Size";
const char* FILTER_REPETITION = u8"Filter repetition";
const char* AUTO_ATTACH = u8"Auto attach";
const char* ATTACH_SAVED_ONLY = u8"Auto attach (saved only)";
const char* DEFAULT_CODEPAGE = u8"Default codepage";
const char* FLUSH_DELAY = u8"Flush delay";
const char* MAX_BUFFER_SIZE = u8"Max buffer size";
const wchar_t* CONSOLE = L"Console";
const wchar_t* CLIPBOARD = L"Clipboard";
const wchar_t* ABOUT = L"Textractor " ARCH L" v" VERSION LR"( made by me: Artikash (email: akashmozumdar@gmail.com)