mirror of
https://github.com/Artikash/Textractor.git
synced 2024-12-23 17:04:12 +08:00
Merge branch 'master' into master
This commit is contained in:
commit
7b602393de
@ -118,7 +118,7 @@ bool ExtenWindow::eventFilter(QObject* target, QEvent* event)
|
||||
|
||||
void ExtenWindow::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
if (event->key() == Qt::Key_Delete && ui->extenList->currentItem() != nullptr)
|
||||
if (event->key() == Qt::Key_Delete && ui->extenList->currentItem())
|
||||
{
|
||||
Unload(ui->extenList->currentIndex().row());
|
||||
Sync();
|
||||
|
@ -29,7 +29,7 @@ namespace
|
||||
|
||||
TextHook GetHook(uint64_t addr)
|
||||
{
|
||||
if (view == nullptr) return {};
|
||||
if (!view) return {};
|
||||
std::scoped_lock lock(viewMutex);
|
||||
for (auto hook : view)
|
||||
if (hook.address == addr) return hook;
|
||||
@ -60,7 +60,7 @@ namespace
|
||||
|
||||
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;
|
||||
Synchronized<std::unordered_map<DWORD, ProcessRecord>> processRecordsByIds;
|
||||
|
||||
Host::ProcessEventHandler OnConnect, OnDisconnect;
|
||||
Host::ThreadEventHandler OnCreate, OnDestroy;
|
||||
@ -102,10 +102,10 @@ namespace
|
||||
case HOST_NOTIFICATION_FOUND_HOOK:
|
||||
{
|
||||
auto info = *(HookFoundNotif*)buffer;
|
||||
auto& OnHookFound = processRecordsByIds->at(processId).OnHookFound;
|
||||
auto OnHookFound = processRecordsByIds->at(processId).OnHookFound;
|
||||
std::wstring wide = info.text;
|
||||
if (wide.size() > STRING) OnHookFound(info.hp, info.text);
|
||||
info.hp.type = USING_STRING;
|
||||
info.hp.type &= ~USING_UNICODE;
|
||||
if (auto converted = Util::StringToWideString((char*)info.text, Host::defaultCodepage))
|
||||
if (converted->size() > STRING) OnHookFound(info.hp, converted.value());
|
||||
info.hp.codepage = CP_UTF8;
|
||||
@ -132,7 +132,7 @@ namespace
|
||||
auto textThread = textThreadsByParams->find(tp);
|
||||
if (textThread == textThreadsByParams->end())
|
||||
{
|
||||
try { textThread = textThreadsByParams->try_emplace(tp, tp, Host::GetHookParam(tp)).first; }
|
||||
try { textThread = textThreadsByParams->try_emplace(tp, tp, processRecordsByIds->at(tp.processId).GetHook(tp.addr).hp).first; }
|
||||
catch (std::out_of_range) { continue; } // probably garbage data in pipe, try again
|
||||
OnCreate(textThread->second);
|
||||
}
|
||||
@ -158,8 +158,6 @@ namespace Host
|
||||
OnDestroy = [Destroy](TextThread& thread) { thread.Stop(); Destroy(thread); };
|
||||
TextThread::Output = Output;
|
||||
|
||||
processRecordsByIds->try_emplace(console.processId, console.processId, INVALID_HANDLE_VALUE);
|
||||
OnConnect(console.processId);
|
||||
textThreadsByParams->try_emplace(console, console, HookParam{}, CONSOLE);
|
||||
OnCreate(GetThread(console));
|
||||
textThreadsByParams->try_emplace(clipboard, clipboard, HookParam{}, CLIPBOARD);
|
||||
@ -233,16 +231,18 @@ namespace Host
|
||||
processRecordsByIds->at(processId).Send(FindHookCmd(sp));
|
||||
}
|
||||
|
||||
HookParam GetHookParam(ThreadParam tp)
|
||||
{
|
||||
return processRecordsByIds->at(tp.processId).GetHook(tp.addr).hp;
|
||||
}
|
||||
|
||||
TextThread& GetThread(ThreadParam tp)
|
||||
{
|
||||
return textThreadsByParams->at(tp);
|
||||
}
|
||||
|
||||
TextThread* GetThread(int64_t handle)
|
||||
{
|
||||
auto textThreadsByParams = ::textThreadsByParams.Acquire();
|
||||
auto thread = std::find_if(textThreadsByParams->begin(), textThreadsByParams->end(), [&](const auto& thread) { return thread.second.handle == handle; });
|
||||
return thread != textThreadsByParams->end() ? &thread->second : nullptr;
|
||||
}
|
||||
|
||||
void AddConsoleOutput(std::wstring text)
|
||||
{
|
||||
GetThread(console).AddSentence(std::move(text));
|
||||
|
@ -12,11 +12,12 @@ namespace Host
|
||||
|
||||
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(int64_t handle);
|
||||
TextThread& GetThread(ThreadParam tp);
|
||||
|
||||
void AddConsoleOutput(std::wstring text);
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
#include <QListWidget>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QMessageBox>
|
||||
#include <QInputDialog>
|
||||
#include <QFileDialog>
|
||||
@ -21,7 +22,7 @@ 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* SEARCH_FOR_HOOKS;
|
||||
extern const char* SETTINGS;
|
||||
extern const char* EXTENSIONS;
|
||||
extern const char* SELECT_PROCESS;
|
||||
@ -30,6 +31,7 @@ extern const char* SEARCH_GAME;
|
||||
extern const char* PROCESSES;
|
||||
extern const char* CODE_INFODUMP;
|
||||
extern const char* HOOK_SEARCH_UNSTABLE_WARNING;
|
||||
extern const char* SEARCH_CJK;
|
||||
extern const char* SEARCH_PATTERN;
|
||||
extern const char* SEARCH_DURATION;
|
||||
extern const char* PATTERN_OFFSET;
|
||||
@ -59,14 +61,14 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
extenWindow(new ExtenWindow(this))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
for (auto[text, slot] : Array<std::tuple<QString, void(MainWindow::*)()>>{
|
||||
for (auto [text, slot] : Array<std::tuple<QString, void(MainWindow::*)()>>{
|
||||
{ ATTACH, &MainWindow::AttachProcess },
|
||||
{ LAUNCH, &MainWindow::LaunchProcess },
|
||||
{ DETACH, &MainWindow::DetachProcess },
|
||||
{ ADD_HOOK, &MainWindow::AddHook },
|
||||
{ REMOVE_HOOKS, &MainWindow::RemoveHooks },
|
||||
{ SAVE_HOOKS, &MainWindow::SaveHooks },
|
||||
{ FIND_HOOKS, &MainWindow::FindHooks },
|
||||
{ SEARCH_FOR_HOOKS, &MainWindow::FindHooks },
|
||||
{ SETTINGS, &MainWindow::Settings },
|
||||
{ EXTENSIONS, &MainWindow::Extensions }
|
||||
})
|
||||
@ -232,6 +234,13 @@ DWORD MainWindow::GetSelectedProcessId()
|
||||
|
||||
std::array<InfoForExtension, 10> MainWindow::GetMiscInfo(TextThread& thread)
|
||||
{
|
||||
void(*AddSentence)(MainWindow*, int64_t, const wchar_t*) = [](MainWindow* This, int64_t number, const wchar_t* sentence)
|
||||
{
|
||||
std::wstring sentenceStr = sentence;
|
||||
// pointer from Host::GetThread may not stay valid unless on main thread
|
||||
QMetaObject::invokeMethod(This, [=]() mutable { if (TextThread* thread = Host::GetThread(number)) thread->AddSentence(std::move(sentenceStr)); });
|
||||
};
|
||||
|
||||
return
|
||||
{ {
|
||||
{ "current select", &thread == current },
|
||||
@ -240,6 +249,8 @@ std::array<InfoForExtension, 10> MainWindow::GetMiscInfo(TextThread& thread)
|
||||
{ "hook address", (int64_t)thread.tp.addr },
|
||||
{ "text handle", thread.handle },
|
||||
{ "text name", (int64_t)thread.name.c_str() },
|
||||
{ "this", (int64_t)this },
|
||||
{ "void (*AddSentence)(void* this, int64_t number, const wchar_t* sentence)", (int64_t)AddSentence },
|
||||
{ nullptr, 0 } // nullptr marks end of info array
|
||||
} };
|
||||
}
|
||||
@ -321,13 +332,14 @@ void MainWindow::RemoveHooks()
|
||||
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);
|
||||
if (tp.processId == GetSelectedProcessId()) hooks[tp.addr] = Host::GetThread(tp).hp;
|
||||
}
|
||||
auto hookList = new QListWidget(this);
|
||||
hookList->setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint);
|
||||
hookList->setAttribute(Qt::WA_DeleteOnClose);
|
||||
hookList->setMinimumSize({ 300, 50 });
|
||||
hookList->setWindowTitle(DOUBLE_CLICK_TO_REMOVE_HOOK);
|
||||
for (auto[address, hp] : hooks)
|
||||
for (auto [address, hp] : hooks)
|
||||
new QListWidgetItem(QString(hp.name) + "@" + QString::number(address, 16), hookList);
|
||||
connect(hookList, &QListWidget::itemDoubleClicked, [processId, hookList](QListWidgetItem* item)
|
||||
{
|
||||
@ -343,141 +355,143 @@ void MainWindow::RemoveHooks()
|
||||
|
||||
void MainWindow::SaveHooks()
|
||||
{
|
||||
if (auto processName = Util::GetModuleFilename(GetSelectedProcessId()))
|
||||
{
|
||||
auto processName = Util::GetModuleFilename(GetSelectedProcessId());
|
||||
if (!processName) return;
|
||||
QHash<uint64_t, QString> hookCodes;
|
||||
for (int i = 0; i < ui->ttCombo->count(); ++i)
|
||||
{
|
||||
ThreadParam tp = ParseTextThreadString(ui->ttCombo->itemText(i));
|
||||
if (tp.processId == GetSelectedProcessId())
|
||||
{
|
||||
HookParam hp = Host::GetHookParam(tp);
|
||||
HookParam hp = Host::GetThread(tp).hp;
|
||||
if (!(hp.type & HOOK_ENGINE)) hookCodes[tp.addr] = S(Util::GenerateCode(hp, tp.processId));
|
||||
}
|
||||
}
|
||||
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::GetHookParam(tp), tp.processId)));
|
||||
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)));
|
||||
QTextFile(HOOK_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Append).write((hookInfo.join(" , ") + "\n").toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::FindHooks()
|
||||
{
|
||||
QMessageBox::information(this, FIND_HOOKS, HOOK_SEARCH_UNSTABLE_WARNING);
|
||||
struct : QDialog
|
||||
QMessageBox::information(this, SEARCH_FOR_HOOKS, HOOK_SEARCH_UNSTABLE_WARNING);
|
||||
|
||||
DWORD processId = GetSelectedProcessId();
|
||||
SearchParam sp = {};
|
||||
bool customSettings = false;
|
||||
std::wregex filter(L".");
|
||||
|
||||
QDialog dialog(this, Qt::WindowCloseButtonHint);
|
||||
QFormLayout layout(&dialog);
|
||||
QCheckBox cjkCheckbox(&dialog);
|
||||
layout.addRow(SEARCH_CJK, &cjkCheckbox);
|
||||
QDialogButtonBox confirm(QDialogButtonBox::Ok | QDialogButtonBox::Help, &dialog);
|
||||
layout.addRow(&confirm);
|
||||
confirm.button(QDialogButtonBox::Ok)->setText(START_HOOK_SEARCH);
|
||||
confirm.button(QDialogButtonBox::Help)->setText(SETTINGS);
|
||||
connect(&confirm, &QDialogButtonBox::helpRequested, [&customSettings] { customSettings = true; });
|
||||
connect(&confirm, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
|
||||
connect(&confirm, &QDialogButtonBox::helpRequested, &dialog, &QDialog::accept);
|
||||
dialog.setWindowTitle(SEARCH_FOR_HOOKS);
|
||||
if (dialog.exec() == QDialog::Rejected) return;
|
||||
|
||||
if (customSettings)
|
||||
{
|
||||
using QDialog::QDialog;
|
||||
void launch()
|
||||
{
|
||||
auto layout = new QFormLayout(this);
|
||||
auto patternInput = new QLineEdit(x64 ? "CC CC 48 89" : "CC CC 55 8B EC", this);
|
||||
layout->addRow(SEARCH_PATTERN, patternInput);
|
||||
for (auto[value, label] : Array<std::tuple<int&, const char*>>{
|
||||
QDialog dialog(this, Qt::WindowCloseButtonHint);
|
||||
QFormLayout layout(&dialog);
|
||||
QLineEdit patternInput(x64 ? "CC CC 48 89" : "CC CC 55 8B EC", &dialog);
|
||||
layout.addRow(SEARCH_PATTERN, &patternInput);
|
||||
for (auto [value, label] : Array<std::tuple<int&, const char*>>{
|
||||
{ sp.searchTime = 20000, SEARCH_DURATION },
|
||||
{ sp.offset = 2, PATTERN_OFFSET },
|
||||
})
|
||||
{
|
||||
auto spinBox = new QSpinBox(this);
|
||||
auto spinBox = new QSpinBox(&dialog);
|
||||
spinBox->setMaximum(INT_MAX);
|
||||
spinBox->setValue(value);
|
||||
layout->addRow(label, spinBox);
|
||||
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [=, &value] { value = spinBox->value(); });
|
||||
layout.addRow(label, spinBox);
|
||||
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [&value] (int newValue) { value = newValue; });
|
||||
}
|
||||
for (auto[value, label] : Array<std::tuple<uintptr_t&, const char*>>{
|
||||
for (auto [value, label] : Array<std::tuple<uintptr_t&, const char*>>{
|
||||
{ sp.minAddress = 0, MIN_ADDRESS },
|
||||
{ sp.maxAddress = -1ULL, MAX_ADDRESS },
|
||||
{ sp.padding = 0, STRING_OFFSET }
|
||||
})
|
||||
{
|
||||
auto input = new QLineEdit(QString::number(value, 16), this);
|
||||
layout->addRow(label, input);
|
||||
auto input = new QLineEdit(QString::number(value, 16), &dialog);
|
||||
layout.addRow(label, input);
|
||||
connect(input, &QLineEdit::textEdited, [&value](QString input)
|
||||
{
|
||||
bool ok;
|
||||
if (uintptr_t newValue = input.toULongLong(&ok, 16); ok) value = newValue;
|
||||
});
|
||||
}
|
||||
auto filterInput = new QLineEdit(this);
|
||||
layout->addRow(HOOK_SEARCH_FILTER, filterInput);
|
||||
auto save = new QPushButton(START_HOOK_SEARCH, this);
|
||||
layout->addWidget(save);
|
||||
connect(save, &QPushButton::clicked, this, &QDialog::accept);
|
||||
connect(save, &QPushButton::clicked, [this, patternInput, filterInput]
|
||||
{
|
||||
QByteArray pattern = QByteArray::fromHex(patternInput->text().replace("??", QString::number(XX, 16)).toUtf8());
|
||||
if (pattern.size() < 3) return;
|
||||
std::wregex filter(L".");
|
||||
if (!filterInput->text().isEmpty()) try { filter = std::wregex(S(filterInput->text())); } catch (std::regex_error) {};
|
||||
QLineEdit filterInput(".", &dialog);
|
||||
layout.addRow(HOOK_SEARCH_FILTER, &filterInput);
|
||||
QPushButton startButton(START_HOOK_SEARCH, &dialog);
|
||||
layout.addWidget(&startButton);
|
||||
connect(&startButton, &QPushButton::clicked, &dialog, &QDialog::accept);
|
||||
if (dialog.exec() == QDialog::Rejected) 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) {};
|
||||
}
|
||||
else
|
||||
{
|
||||
// sp.length is 0 in this branch, so default will be used
|
||||
filter = cjkCheckbox.isChecked() ? std::wregex(L"[\\u3000-\\ua000]{4,}") : std::wregex(L"[\\u0020-\\u1000]{4,}");
|
||||
}
|
||||
|
||||
auto hooks = std::make_shared<QString>();
|
||||
DWORD processId = this->processId;
|
||||
try
|
||||
{
|
||||
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");
|
||||
});
|
||||
}
|
||||
catch (std::out_of_range) { return; }
|
||||
QString fileName = QFileDialog::getSaveFileName(this, SAVE_SEARCH_RESULTS, "./Hooks.txt", TEXT_FILES);
|
||||
if (fileName.isEmpty()) fileName = "Hooks.txt";
|
||||
std::thread([hooks, fileName]
|
||||
} catch (std::out_of_range) { return; }
|
||||
QString saveFile = QFileDialog::getSaveFileName(this, SAVE_SEARCH_RESULTS, "./Hooks.txt", TEXT_FILES);
|
||||
if (saveFile.isEmpty()) saveFile = "Hooks.txt";
|
||||
std::thread([hooks, saveFile]
|
||||
{
|
||||
for (int lastSize = 0; hooks->size() == 0 || hooks->size() != lastSize; Sleep(2000)) lastSize = hooks->size();
|
||||
QTextFile(fileName, QIODevice::WriteOnly | QIODevice::Truncate).write(hooks->toUtf8());
|
||||
QTextFile(saveFile, QIODevice::WriteOnly | QIODevice::Truncate).write(hooks->toUtf8());
|
||||
hooks->clear();
|
||||
}).detach();
|
||||
});
|
||||
setWindowTitle(FIND_HOOKS);
|
||||
exec();
|
||||
}
|
||||
|
||||
SearchParam sp = {};
|
||||
DWORD processId;
|
||||
} searchDialog(this, Qt::WindowCloseButtonHint);
|
||||
searchDialog.processId = GetSelectedProcessId();
|
||||
searchDialog.launch();
|
||||
}
|
||||
|
||||
void MainWindow::Settings()
|
||||
{
|
||||
struct : QDialog
|
||||
{
|
||||
using QDialog::QDialog;
|
||||
void launch()
|
||||
{
|
||||
auto settings = new QSettings(CONFIG_FILE, QSettings::IniFormat, this);
|
||||
auto layout = new QFormLayout(this);
|
||||
auto save = new QPushButton(SAVE_SETTINGS, this);
|
||||
layout->addWidget(save);
|
||||
for (auto[value, label] : Array<std::tuple<int&, const char*>>{
|
||||
QDialog dialog(this, Qt::WindowCloseButtonHint);
|
||||
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<int&, const char*>>{
|
||||
{ Host::defaultCodepage, DEFAULT_CODEPAGE },
|
||||
{ TextThread::maxBufferSize, MAX_BUFFER_SIZE },
|
||||
{ TextThread::flushDelay, FLUSH_DELAY },
|
||||
})
|
||||
{
|
||||
auto spinBox = new QSpinBox(this);
|
||||
auto spinBox = new QSpinBox(&dialog);
|
||||
spinBox->setMaximum(INT_MAX);
|
||||
spinBox->setValue(value);
|
||||
layout->insertRow(0, label, spinBox);
|
||||
connect(save, &QPushButton::clicked, [=, &value] { settings->setValue(label, value = spinBox->value()); });
|
||||
layout.insertRow(0, 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*>>{
|
||||
for (auto [value, label] : Array<std::tuple<bool&, const char*>>{
|
||||
{ TextThread::filterRepetition, FILTER_REPETITION },
|
||||
})
|
||||
{
|
||||
auto checkBox = new QCheckBox(this);
|
||||
auto checkBox = new QCheckBox(&dialog);
|
||||
checkBox->setChecked(value);
|
||||
layout->insertRow(0, label, checkBox);
|
||||
connect(save, &QPushButton::clicked, [=, &value] { settings->setValue(label, value = checkBox->isChecked()); });
|
||||
layout.insertRow(0, label, checkBox);
|
||||
connect(&saveButton, &QPushButton::clicked, [checkBox, label, &settings, &value] { settings.setValue(label, value = checkBox->isChecked()); });
|
||||
}
|
||||
connect(save, &QPushButton::clicked, this, &QDialog::accept);
|
||||
setWindowTitle(SETTINGS);
|
||||
exec();
|
||||
}
|
||||
} settingsDialog(this, Qt::WindowCloseButtonHint);
|
||||
settingsDialog.launch();
|
||||
connect(&saveButton, &QPushButton::clicked, &dialog, &QDialog::accept);
|
||||
dialog.setWindowTitle(SETTINGS);
|
||||
dialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::Extensions()
|
||||
|
@ -14,7 +14,7 @@ struct SentenceInfo
|
||||
// nullptr marks end of info array
|
||||
int64_t operator[](std::string propertyName)
|
||||
{
|
||||
for (auto info = infoArray; info->name != nullptr; ++info) if (propertyName == info->name) return info->value;
|
||||
for (auto info = infoArray; info->name; ++info) if (propertyName == info->name) return info->value;
|
||||
throw;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,10 @@
|
||||
#include <QMenu>
|
||||
#include <QLayout>
|
||||
#include <QLabel>
|
||||
#include <QFormLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QSpinBox>
|
||||
#include <QPushButton>
|
||||
#include <QPainter>
|
||||
#include <QMouseEvent>
|
||||
#include <QSettings>
|
||||
@ -19,7 +23,11 @@ extern const char* SHOW_ORIGINAL_INFO;
|
||||
extern const char* SIZE_LOCK;
|
||||
extern const char* BG_COLOR;
|
||||
extern const char* TEXT_COLOR;
|
||||
extern const char* FONT;
|
||||
extern const char* FONT_SIZE;
|
||||
extern const char* FONT_FAMILY;
|
||||
extern const char* FONT_WEIGHT;
|
||||
extern const char* SAVE_SETTINGS;
|
||||
|
||||
std::mutex m;
|
||||
|
||||
@ -57,12 +65,32 @@ public:
|
||||
display->setPalette(newPalette);
|
||||
settings->setValue(TEXT_COLOR, color);
|
||||
};
|
||||
auto setFontSize = [=](int pt)
|
||||
auto requestFont = [=]
|
||||
{
|
||||
QFont newFont = display->font();
|
||||
newFont.setPointSize(pt);
|
||||
display->setFont(newFont);
|
||||
settings->setValue(FONT_SIZE, pt);
|
||||
QFont font = display->font();
|
||||
auto fontDialog = new QDialog(this, Qt::WindowCloseButtonHint);
|
||||
fontDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
fontDialog->setWindowTitle(FONT);
|
||||
auto layout = new QFormLayout(fontDialog);
|
||||
fontDialog->setLayout(layout);
|
||||
auto fontFamily = new QLineEdit(font.family(), fontDialog);
|
||||
layout->addRow(FONT_FAMILY, fontFamily);
|
||||
auto fontSize = new QSpinBox(fontDialog);
|
||||
fontSize->setValue(font.pointSize());
|
||||
layout->addRow(FONT_SIZE, fontSize);
|
||||
auto fontWeight = new QSpinBox(fontDialog);
|
||||
fontWeight->setValue(font.weight());
|
||||
layout->addRow(FONT_WEIGHT, fontWeight);
|
||||
auto save = new QPushButton(SAVE_SETTINGS, fontDialog);
|
||||
layout->addWidget(save);
|
||||
connect(save, &QPushButton::clicked, fontDialog, &QDialog::accept);
|
||||
fontDialog->open();
|
||||
connect(fontDialog, &QDialog::accepted, [=]
|
||||
{
|
||||
QFont font(fontFamily->text(), fontSize->value(), fontWeight->value());
|
||||
settings->setValue(FONT, font.toString());
|
||||
display->setFont(font);
|
||||
});
|
||||
};
|
||||
auto setTopmost = [=](bool topmost)
|
||||
{
|
||||
@ -83,7 +111,10 @@ public:
|
||||
setGeometry(settings->value(WINDOW, geometry()).toRect());
|
||||
setLock(settings->value(SIZE_LOCK, false).toBool());
|
||||
setTopmost(settings->value(TOPMOST, false).toBool());
|
||||
setFontSize(settings->value(FONT_SIZE, 16).toInt());
|
||||
QFont font = display->font();
|
||||
font.setPointSize(16);
|
||||
font.fromString(settings->value(FONT, font.toString()).toString());
|
||||
display->setFont(font);
|
||||
setBackgroundColor(settings->value(BG_COLOR, palette().window().color()).value<QColor>());
|
||||
setTextColor(settings->value(TEXT_COLOR, display->palette().windowText().color()).value<QColor>());
|
||||
|
||||
@ -99,7 +130,7 @@ public:
|
||||
showOriginal->setChecked(settings->value(SHOW_ORIGINAL, true).toBool());
|
||||
menu->addAction(BG_COLOR, [=] { setBackgroundColor(QColorDialog::getColor(bgColor, this, BG_COLOR, QColorDialog::ShowAlphaChannel)); });
|
||||
menu->addAction(TEXT_COLOR, [=] { setTextColor(QColorDialog::getColor(display->palette().windowText().color(), this, TEXT_COLOR, QColorDialog::ShowAlphaChannel)); });
|
||||
menu->addAction(FONT_SIZE, [=] { setFontSize(QInputDialog::getInt(this, FONT_SIZE, "", display->font().pointSize(), 0, INT_MAX, 1, nullptr, Qt::WindowCloseButtonHint)); });
|
||||
menu->addAction(FONT, requestFont);
|
||||
display->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(display, &QLabel::customContextMenuRequested, [=](QPoint point) { menu->exec(mapToGlobal(point)); });
|
||||
connect(this, &QDialog::destroyed, [=] { settings->setValue(WINDOW, geometry()); });
|
||||
@ -147,7 +178,7 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
||||
case DLL_PROCESS_DETACH:
|
||||
{
|
||||
std::lock_guard l(m);
|
||||
if (window != nullptr)
|
||||
if (window)
|
||||
{
|
||||
window->settings->setValue(WINDOW, window->geometry());
|
||||
window->settings->sync();
|
||||
|
@ -127,7 +127,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||
}
|
||||
lua_pushstring(L, WideStringToString(sentence).c_str());
|
||||
lua_createtable(L, 0, 0);
|
||||
for (auto info = sentenceInfo.infoArray; info->name != nullptr; ++info)
|
||||
for (auto info = sentenceInfo.infoArray; info->name; ++info)
|
||||
{
|
||||
lua_pushstring(L, info->name);
|
||||
lua_pushinteger(L, info->value);
|
||||
|
@ -93,13 +93,11 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
||||
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||
{
|
||||
std::lock_guard l(m);
|
||||
static std::unordered_map<int64_t, std::wstring> queuedWritesByHandle;
|
||||
int64_t textHandle = sentenceInfo["text handle"];
|
||||
int64_t textHandle = sentenceInfo["text number"];
|
||||
|
||||
for (auto linkedHandle : linkedTextHandles[textHandle]) queuedWritesByHandle[linkedHandle] += L"\n" + sentence;
|
||||
for (auto linkedHandle : linkedTextHandles[textHandle])
|
||||
((void(*)(void*, int64_t, const wchar_t*))sentenceInfo["void (*AddSentence)(void* this, int64_t number, const wchar_t* sentence)"])
|
||||
((void*)sentenceInfo["this"], linkedHandle, sentence.c_str());
|
||||
|
||||
if (queuedWritesByHandle[textHandle].empty()) return false;
|
||||
sentence += queuedWritesByHandle[textHandle];
|
||||
queuedWritesByHandle[textHandle].clear();
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "network.h"
|
||||
#include <QTimer>
|
||||
#include <QInputDialog>
|
||||
#include <QFile>
|
||||
|
||||
extern const char* SELECT_LANGUAGE;
|
||||
extern const char* SELECT_LANGUAGE_MESSAGE;
|
||||
@ -12,6 +13,8 @@ extern QStringList languages;
|
||||
extern Synchronized<std::wstring> translateTo;
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text);
|
||||
|
||||
Synchronized<std::unordered_map<std::wstring, std::wstring>> translationCache;
|
||||
|
||||
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||
{
|
||||
switch (ul_reason_for_call)
|
||||
@ -33,10 +36,21 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
||||
.toStdWString()
|
||||
);
|
||||
});
|
||||
|
||||
QFile file(QString("%1 Cache.txt").arg(TRANSLATION_PROVIDER));
|
||||
file.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
QStringList savedCache = QString(file.readAll()).split("|T|\n", QString::SkipEmptyParts);
|
||||
for (int i = 0; i < savedCache.size() - 1; i += 2)
|
||||
translationCache->insert({ savedCache[i].toStdWString(), savedCache[i + 1].toStdWString() });
|
||||
}
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
{
|
||||
QFile file(QString("%1 Cache.txt").arg(TRANSLATION_PROVIDER));
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Text);
|
||||
auto translationCache = ::translationCache.Acquire();
|
||||
for (const auto& [original, translation] : translationCache.contents)
|
||||
file.write(QString::fromStdWString(FormatString(L"%s|T|\n%s|T|\n", original, translation)).toUtf8());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -63,7 +77,6 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||
const int tokenCount = 30, delay = 60 * 1000;
|
||||
Synchronized<std::vector<DWORD>> tokens;
|
||||
} rateLimiter;
|
||||
static Synchronized<std::unordered_map<std::wstring, std::wstring>> translationCache;
|
||||
|
||||
bool cache = false;
|
||||
std::wstring translation;
|
||||
|
@ -61,11 +61,12 @@ struct ThreadParam
|
||||
|
||||
struct SearchParam
|
||||
{
|
||||
BYTE pattern[25] = {}; // pattern in memory to search for
|
||||
int length, // length of pattern
|
||||
BYTE pattern[25]; // pattern in memory to search for
|
||||
int length, // length of pattern (zero means this SearchParam is invalid and the default should be used)
|
||||
offset, // offset from start of pattern to add hook
|
||||
searchTime; // ms
|
||||
uintptr_t padding, minAddress, maxAddress;
|
||||
void(*hookPostProcesser)(HookParam&);
|
||||
};
|
||||
|
||||
struct InsertHookCmd // From host
|
||||
|
@ -43,7 +43,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int)
|
||||
return FALSE;
|
||||
}, 0), SW_SHOW);
|
||||
|
||||
std::thread([] { while (true) Sleep(vars.at(0)), lstrlenW(L"こんにちは"); }).detach();
|
||||
std::thread([] { while (true) Sleep(vars.at(0)), lstrlenW(L"こんにちは\n (Hello)"); }).detach();
|
||||
|
||||
STARTUPINFOW info = { sizeof(info) };
|
||||
wchar_t commandLine[] = { L"Textractor -p\"Test.exe\"" };
|
||||
|
6
text.cpp
6
text.cpp
@ -12,7 +12,7 @@ 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* SEARCH_FOR_HOOKS = u8"Search for hooks";
|
||||
const char* SETTINGS = u8"Settings";
|
||||
const char* EXTENSIONS = u8"Extensions";
|
||||
const char* SELECT_PROCESS = u8"Select process";
|
||||
@ -51,6 +51,7 @@ const char* CONFIRM_EXTENSION_OVERWRITE = u8"Another version of this extension a
|
||||
const char* EXTENSION_WRITE_ERROR = u8"Failed to save extension";
|
||||
const char* USE_JP_LOCALE = u8"Emulate japanese locale?";
|
||||
const char* HOOK_SEARCH_UNSTABLE_WARNING = u8"Searching for hooks is unstable! Be prepared for your game to crash!";
|
||||
const char* SEARCH_CJK = u8"Search for Chinese/Japanese/Korean";
|
||||
const char* SEARCH_PATTERN = u8"Search pattern (hex byte array)";
|
||||
const char* SEARCH_DURATION = u8"Search duration (ms)";
|
||||
const char* PATTERN_OFFSET = u8"Offset from pattern start";
|
||||
@ -115,7 +116,10 @@ Only works if this extension is used directly after a translation extension)";
|
||||
const char* SIZE_LOCK = u8"Size Locked";
|
||||
const char* BG_COLOR = u8"Background Color";
|
||||
const char* TEXT_COLOR = u8"Text Color";
|
||||
const char* FONT = u8"Font";
|
||||
const char* FONT_FAMILY = u8"Font Family";
|
||||
const char* FONT_SIZE = u8"Font Size";
|
||||
const char* FONT_WEIGHT = u8"Font Weight";
|
||||
const char* LUA_INTRO = u8R"(--[[
|
||||
ProcessSentence is called each time Textractor receives a sentence of text.
|
||||
|
||||
|
@ -16818,7 +16818,7 @@ bool InsertVanillawareGCHook()
|
||||
|
||||
/** Artikash 6/7/2019
|
||||
* PPSSPP JIT code has pointers, but they are all added to an offset before being used.
|
||||
Find that offset and report it to user so they can search for hooks properly.
|
||||
Find that offset so that hook searching works properly.
|
||||
To find the offset, find a page of mapped memory with size 0x1f00000, read and write permissions, take its address and subtract 0x8000000.
|
||||
The above is useful for emulating PSP hardware, so unlikely to change between versions.
|
||||
*/
|
||||
@ -16839,7 +16839,13 @@ bool FindPPSSPP()
|
||||
if (info.RegionSize == 0x1f00000 && info.Protect == PAGE_READWRITE && info.Type == MEM_MAPPED)
|
||||
{
|
||||
found = true;
|
||||
ConsoleOutput("Textractor: PPSSPP memory found: use pattern 79 0F C7 85 and pattern offset 0 and string offset 0x%p to search for hooks", probe - 0x8000000);
|
||||
ConsoleOutput("Textractor: PPSSPP memory found: searching for hooks should yield working hook codes");
|
||||
memcpy(spDefault.pattern, Array<BYTE>{ 0x79, 0x0f, 0xc7, 0x85 }, spDefault.length = 4);
|
||||
spDefault.offset = 0;
|
||||
spDefault.minAddress = 0;
|
||||
spDefault.maxAddress = -1ULL;
|
||||
spDefault.padding = (uintptr_t)probe - 0x8000000;
|
||||
spDefault.hookPostProcesser = [](HookParam& hp) { hp.type |= NO_CONTEXT; };
|
||||
}
|
||||
probe += info.RegionSize;
|
||||
}
|
||||
|
@ -37,8 +37,8 @@ namespace Engine
|
||||
|
||||
void Hijack()
|
||||
{
|
||||
static bool hijacked = false;
|
||||
if (hijacked) return;
|
||||
static auto _ = []
|
||||
{
|
||||
GetModuleFileNameW(nullptr, processPath, MAX_PATH);
|
||||
processName = wcsrchr(processPath, L'\\') + 1;
|
||||
|
||||
@ -50,9 +50,12 @@ namespace Engine
|
||||
processStopAddress = (uintptr_t)info.BaseAddress + info.RegionSize;
|
||||
} while (info.Protect > PAGE_NOACCESS);
|
||||
processStopAddress -= info.RegionSize;
|
||||
spDefault.minAddress = processStartAddress;
|
||||
spDefault.maxAddress = processStopAddress;
|
||||
ConsoleOutput("Textractor: hijacking process located from 0x%p to 0x%p", processStartAddress, processStopAddress);
|
||||
|
||||
DetermineEngineType();
|
||||
hijacked = true;
|
||||
ConsoleOutput("Textractor: finished hijacking process located from 0x%p to 0x%p", processStartAddress, processStopAddress);
|
||||
return NULL;
|
||||
}();
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ namespace Engine
|
||||
{
|
||||
/** Artikash 6/7/2019
|
||||
* PPSSPP JIT code has pointers, but they are all added to an offset before being used.
|
||||
Find that offset and report it to user so they can search for hooks properly.
|
||||
Find that offset so that hook searching works properly.
|
||||
To find the offset, find a page of mapped memory with size 0x1f00000, read and write permissions, take its address and subtract 0x8000000.
|
||||
The above is useful for emulating PSP hardware, so unlikely to change between versions.
|
||||
*/
|
||||
@ -29,7 +29,13 @@ namespace Engine
|
||||
if (info.RegionSize == 0x1f00000 && info.Protect == PAGE_READWRITE && info.Type == MEM_MAPPED)
|
||||
{
|
||||
found = true;
|
||||
ConsoleOutput("Textractor: PPSSPP memory found: use pattern 79 10 41 C7 and pattern offset 0 and string offset 0x%p to search for hooks", probe - 0x8000000);
|
||||
ConsoleOutput("Textractor: PPSSPP memory found: searching for hooks should yield working hook codes");
|
||||
memcpy(spDefault.pattern, Array<BYTE>{ 0x79, 0x10, 0x41, 0xc7 }, spDefault.length = 4);
|
||||
spDefault.offset = 0;
|
||||
spDefault.minAddress = 0;
|
||||
spDefault.maxAddress = -1ULL;
|
||||
spDefault.padding = (uintptr_t)probe - 0x8000000;
|
||||
spDefault.hookPostProcesser = [](HookParam& hp) { hp.type |= NO_CONTEXT; };
|
||||
}
|
||||
probe += info.RegionSize;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ extern WinMutex viewMutex;
|
||||
|
||||
namespace
|
||||
{
|
||||
SearchParam current;
|
||||
SearchParam sp;
|
||||
|
||||
constexpr int CACHE_SIZE = 500'000;
|
||||
struct HookRecord
|
||||
@ -23,7 +23,8 @@ namespace
|
||||
hp.offset = offset;
|
||||
hp.type = USING_UNICODE | USING_STRING;
|
||||
hp.address = address;
|
||||
hp.padding = current.padding;
|
||||
hp.padding = sp.padding;
|
||||
if (sp.hookPostProcesser) sp.hookPostProcesser(hp);
|
||||
NotifyHookFound(hp, (wchar_t*)text);
|
||||
}
|
||||
uint64_t address = 0;
|
||||
@ -118,7 +119,7 @@ void Send(char** stack, uintptr_t address)
|
||||
for (int i = -registers; i < 6; ++i)
|
||||
{
|
||||
int length = 0, sum = 0;
|
||||
char* str = stack[i] + current.padding;
|
||||
char* str = stack[i] + sp.padding;
|
||||
__try { for (; (str[length] || str[length + 1]) && length < 500; length += 2) sum += str[length] + str[length + 1]; }
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {}
|
||||
if (length > STRING && length < 499)
|
||||
@ -152,7 +153,7 @@ void Send(char** stack, uintptr_t address)
|
||||
}
|
||||
}
|
||||
|
||||
void SearchForHooks(SearchParam sp)
|
||||
void SearchForHooks(SearchParam spUser)
|
||||
{
|
||||
std::thread([=]
|
||||
{
|
||||
@ -162,7 +163,7 @@ void SearchForHooks(SearchParam sp)
|
||||
try { records = std::make_unique<HookRecord[]>(recordsAvailable = CACHE_SIZE); }
|
||||
catch (std::bad_alloc) { return ConsoleOutput("Textractor: SearchForHooks ERROR (out of memory)"); }
|
||||
|
||||
current = sp;
|
||||
sp = spUser.length == 0 ? spDefault : spUser;
|
||||
|
||||
uintptr_t moduleStartAddress = (uintptr_t)GetModuleHandleW(ITH_DLL);
|
||||
uintptr_t moduleStopAddress = moduleStartAddress;
|
||||
|
@ -165,7 +165,7 @@ void NewHook(HookParam hp, LPCSTR lpname, DWORD flag)
|
||||
WideCharToMultiByte(hp.codepage, 0, hp.text, MAX_MODULE_SIZE, codepageText, MAX_MODULE_SIZE * 4, nullptr, nullptr);
|
||||
if (strlen(utf8Text) < 8 || strlen(codepageText) < 8 || wcslen(hp.text) < 4) return ConsoleOutput(NOT_ENOUGH_TEXT);
|
||||
ConsoleOutput(STARTING_SEARCH);
|
||||
for (auto[addrs, type] : Array<std::tuple<std::vector<uint64_t>, HookParamType>>{
|
||||
for (auto [addrs, type] : Array<std::tuple<std::vector<uint64_t>, HookParamType>>{
|
||||
{ Util::SearchMemory(utf8Text, strlen(utf8Text), PAGE_READWRITE), USING_UTF8 },
|
||||
{ Util::SearchMemory(codepageText, strlen(codepageText), PAGE_READWRITE), USING_STRING },
|
||||
{ Util::SearchMemory(hp.text, wcslen(hp.text) * 2, PAGE_READWRITE), USING_UNICODE }
|
||||
|
@ -14,6 +14,15 @@ void NotifyHookRemove(uint64_t addr, LPCSTR name);
|
||||
void NewHook(HookParam hp, LPCSTR name, DWORD flag = HOOK_ENGINE);
|
||||
void RemoveHook(uint64_t addr, int maxOffset = 9);
|
||||
|
||||
inline SearchParam spDefault = []
|
||||
{
|
||||
SearchParam sp = {};
|
||||
memcpy(sp.pattern, x64 ? Array<BYTE>{ 0xcc, 0xcc, 0x48, 0x89 } : Array<BYTE>{ 0xcc, 0xcc, 0x55, 0x8b, 0xec }, sp.length = x64 ? 4 : 5);
|
||||
sp.offset = 2;
|
||||
sp.searchTime = 20000;
|
||||
return sp;
|
||||
}();
|
||||
|
||||
extern "C" // minhook library
|
||||
{
|
||||
enum MH_STATUS
|
||||
|
@ -327,11 +327,11 @@ int TextHook::GetLength(uintptr_t base, uintptr_t in)
|
||||
|
||||
int TextHook::HookStrlen(BYTE* data)
|
||||
{
|
||||
if (!hp.null_length) return hp.type & USING_UNICODE ? wcslen((wchar_t*)data) * 2 : strlen((char*)data);
|
||||
BYTE* orig = data;
|
||||
int nulls = hp.null_length ? hp.null_length : hp.type & USING_UNICODE ? 2 : 1;
|
||||
for (int nullsRemaining = nulls; nullsRemaining > 0; ++data)
|
||||
for (int nullsRemaining = hp.null_length; nullsRemaining > 0; ++data)
|
||||
if (*data == 0) nullsRemaining -= 1;
|
||||
else nullsRemaining = nulls;
|
||||
else nullsRemaining = hp.null_length;
|
||||
return data - orig;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user