use a default searchparam unless user specifies they want custom settings
This commit is contained in:
parent
ef90382bbb
commit
88b797cd33
@ -105,7 +105,7 @@ namespace
|
||||
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;
|
||||
|
@ -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;
|
||||
@ -66,7 +68,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
{ 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 }
|
||||
})
|
||||
@ -325,6 +327,7 @@ void MainWindow::RemoveHooks()
|
||||
}
|
||||
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)
|
||||
@ -364,25 +367,43 @@ void MainWindow::SaveHooks()
|
||||
|
||||
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);
|
||||
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*>>{
|
||||
{ sp.minAddress = 0, MIN_ADDRESS },
|
||||
@ -390,94 +411,79 @@ void MainWindow::FindHooks()
|
||||
{ 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);
|
||||
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*>>{
|
||||
{ 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()
|
||||
|
@ -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
|
||||
|
3
text.cpp
3
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";
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user