diff --git a/GUI/mainwindow.cpp b/GUI/mainwindow.cpp index 492335f..55099d3 100644 --- a/GUI/mainwindow.cpp +++ b/GUI/mainwindow.cpp @@ -33,6 +33,7 @@ extern const char* PATTERN_OFFSET; extern const char* MIN_ADDRESS; extern const char* MAX_ADDRESS; extern const char* STRING_OFFSET; +extern const char* MAX_HOOK_SEARCH_RECORDS; extern const char* HOOK_SEARCH_FILTER; extern const char* START_HOOK_SEARCH; extern const char* SAVE_SEARCH_RESULTS; @@ -429,22 +430,24 @@ void MainWindow::FindHooks() QDialog dialog(this, Qt::WindowCloseButtonHint); QFormLayout layout(&dialog); QLineEdit patternInput(x64 ? "CC CC 48 89" : "CC CC 55 8B EC", &dialog); + assert(QByteArray::fromHex(patternInput.text().toUtf8()) == QByteArray((const char*)sp.pattern, sp.length)); layout.addRow(SEARCH_PATTERN, &patternInput); for (auto [value, label] : Array>{ - { sp.searchTime = 20000, SEARCH_DURATION }, - { sp.offset = 2, PATTERN_OFFSET }, + { sp.searchTime, SEARCH_DURATION }, + { sp.offset, PATTERN_OFFSET }, + { sp.maxRecords, MAX_HOOK_SEARCH_RECORDS }, }) { auto spinBox = new QSpinBox(&dialog); spinBox->setMaximum(INT_MAX); spinBox->setValue(value); layout.addRow(label, spinBox); - connect(spinBox, qOverload(&QSpinBox::valueChanged), [&value] (int newValue) { value = newValue; }); + connect(spinBox, qOverload(&QSpinBox::valueChanged), [&value](int newValue) { value = newValue; }); } for (auto [value, label] : Array>{ - { sp.minAddress = 0, MIN_ADDRESS }, - { sp.maxAddress = -1ULL, MAX_ADDRESS }, - { sp.padding = 0, STRING_OFFSET } + { sp.minAddress, MIN_ADDRESS }, + { sp.maxAddress, MAX_ADDRESS }, + { sp.padding, STRING_OFFSET }, }) { auto input = new QLineEdit(QString::number(value, 16), &dialog); @@ -463,7 +466,7 @@ void MainWindow::FindHooks() } else { - // sp.length is 0 in this branch, so default will be used + sp.length = 0; // use default filter = std::wregex(cjkCheckbox.isChecked() ? L"[\\u3000-\\ua000]{4,}" : L"[\\u0020-\\u1000]{4,}"); } @@ -476,13 +479,25 @@ void MainWindow::FindHooks() }); } catch (std::out_of_range) { return; } - QString saveFile = QFileDialog::getSaveFileName(this, SAVE_SEARCH_RESULTS, "./Hooks.txt", TEXT_FILES); + QString saveFile = QFileDialog::getSaveFileName(this, SAVE_SEARCH_RESULTS, "./Hooks.txt", TEXT_FILES, nullptr, QFileDialog::DontConfirmOverwrite); if (saveFile.isEmpty()) saveFile = "Hooks.txt"; - std::thread([hooks, saveFile] + std::thread([this, hooks, saveFile] { - for (int lastSize = 0; hooks->size() == 0 || hooks->size() != lastSize; Sleep(2000)) lastSize = hooks->size(); + DWORD64 cleanupTime = GetTickCount64() + 500'000; + for (int lastSize = 0; hooks->size() == 0 || hooks->size() != lastSize; Sleep(2000)) + if (GetTickCount64() > cleanupTime) return; + else lastSize = hooks->size(); + QMetaObject::invokeMethod(this, [this, hooks, saveFile] + { + auto hookList = new QPlainTextEdit(*hooks, this); + hooks->clear(); + hookList->setWindowFlags(Qt::Window | Qt::WindowCloseButtonHint); + hookList->setAttribute(Qt::WA_DeleteOnClose); + hookList->resize({ 750, 300 }); + hookList->setWindowTitle(SEARCH_FOR_HOOKS); + hookList->show(); + }); QTextFile(saveFile, QIODevice::WriteOnly | QIODevice::Truncate).write(hooks->toUtf8()); - hooks->clear(); }).detach(); } diff --git a/include/common.h b/include/common.h index 2a73ec8..4c4d694 100644 --- a/include/common.h +++ b/include/common.h @@ -111,9 +111,3 @@ inline std::wstring FormatString(const wchar_t* format, const Args&... args) #else #define TEST(...) #endif - -#ifdef _DEBUG -#define TEST_SYNC(...) static auto _ = [] { __VA_ARGS__; return 0UL; }(); -#else -#define TEST_SYNC(...) -#endif diff --git a/include/types.h b/include/types.h index 908e02c..05ff97a 100644 --- a/include/types.h +++ b/include/types.h @@ -62,12 +62,13 @@ struct ThreadParam struct SearchParam { - 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&); + BYTE pattern[25] = { 0xcc, 0xcc, x64 ? 0x48 : 0x55, x64 ? 0x89 : 0x8b, 0xec }; // pattern in memory to search for + int length = x64 ? 4 : 5, // length of pattern (zero means this SearchParam is invalid and the default should be used) + offset = 2, // offset from start of pattern to add hook + searchTime = 20000, // ms + maxRecords = 100000; + uintptr_t padding = 0, minAddress = 0, maxAddress = (uintptr_t)-1; + void(*hookPostProcessor)(HookParam&) = nullptr; }; struct InsertHookCmd // From host diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0cd93f8..c4362bc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1 +1,7 @@ +include(QtUtils) +msvc_registry_search() +find_qt5(Core Widgets) + add_executable(Test WIN32 main.cpp resource.rc) + +target_link_libraries(Test Qt5::Widgets) diff --git a/test/main.cpp b/test/main.cpp index 7fa2f31..1a92384 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -4,16 +4,19 @@ #include #include #include +#include wchar_t buffer[1000] = {}; std::array vars = {}; -int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int _) { + QApplication a(_ = 0, DUMMY); + static std::vector>> extensions; for (auto file : std::filesystem::directory_iterator(std::filesystem::current_path())) if (file.path().extension() == L".dll" && (std::stringstream() << std::ifstream(file.path(), std::ios::binary).rdbuf()).str().find("OnNewSentence") != std::string::npos) - LoadLibraryW(file.path().c_str()); + extensions.emplace_back(LoadLibraryW(file.path().c_str())); ShowWindow(CreateDialogParamW(hInstance, MAKEINTRESOURCEW(IDD_DIALOG1), NULL, [](HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) -> INT_PTR { @@ -36,6 +39,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) GetWindowTextW((HWND)lParam, buffer, std::size(buffer)); try { vars.at(LOWORD(wParam) - IDC_EDIT1) = std::stoi(buffer); } catch (...) {} + if (vars.at(1)) extensions.clear(); } } break; diff --git a/text.cpp b/text.cpp index 6cf8d05..d306fde 100644 --- a/text.cpp +++ b/text.cpp @@ -57,6 +57,7 @@ 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"; +const char* MAX_HOOK_SEARCH_RECORDS = u8"Search result cap"; const char* MIN_ADDRESS = u8"Minimum address (hex)"; const char* MAX_ADDRESS = u8"Maximum address (hex)"; const char* STRING_OFFSET = u8"String offset (hex)"; diff --git a/texthook/engine/engine.cc b/texthook/engine/engine.cc index d80de14..385a156 100644 --- a/texthook/engine/engine.cc +++ b/texthook/engine/engine.cc @@ -16845,7 +16845,7 @@ bool FindPPSSPP() spDefault.minAddress = 0; spDefault.maxAddress = -1ULL; spDefault.padding = (uintptr_t)probe - 0x8000000; - spDefault.hookPostProcesser = [](HookParam& hp) { hp.type |= NO_CONTEXT; }; + spDefault.hookPostProcessor = [](HookParam& hp) { hp.type |= NO_CONTEXT; }; } probe += info.RegionSize; } diff --git a/texthook/engine/match64.cc b/texthook/engine/match64.cc index 6a2144f..28f9e5e 100644 --- a/texthook/engine/match64.cc +++ b/texthook/engine/match64.cc @@ -38,7 +38,7 @@ namespace Engine spDefault.minAddress = 0; spDefault.maxAddress = -1ULL; spDefault.padding = (uintptr_t)probe - 0x8000000; - spDefault.hookPostProcesser = [](HookParam& hp) { hp.type |= NO_CONTEXT; }; + spDefault.hookPostProcessor = [](HookParam& hp) { hp.type |= NO_CONTEXT; }; } probe += info.RegionSize; } diff --git a/texthook/hookfinder.cc b/texthook/hookfinder.cc index 985c036..aad6388 100644 --- a/texthook/hookfinder.cc +++ b/texthook/hookfinder.cc @@ -24,7 +24,7 @@ namespace hp.type = USING_UNICODE | USING_STRING; hp.address = address; hp.padding = sp.padding; - if (sp.hookPostProcesser) sp.hookPostProcesser(hp); + if (sp.hookPostProcessor) sp.hookPostProcessor(hp); NotifyHookFound(hp, (wchar_t*)text); } uint64_t address = 0; @@ -160,11 +160,11 @@ void SearchForHooks(SearchParam spUser) static std::mutex m; std::scoped_lock lock(m); - try { records = std::make_unique(recordsAvailable = CACHE_SIZE); } - catch (std::bad_alloc) { return ConsoleOutput("Textractor: SearchForHooks ERROR (out of memory)"); } - sp = spUser.length == 0 ? spDefault : spUser; + try { records = std::make_unique(recordsAvailable = sp.maxRecords); } + catch (std::bad_alloc) { return ConsoleOutput("Textractor: SearchForHooks ERROR (out of memory)"); } + uintptr_t moduleStartAddress = (uintptr_t)GetModuleHandleW(ITH_DLL); uintptr_t moduleStopAddress = moduleStartAddress; MEMORY_BASIC_INFORMATION info; @@ -201,6 +201,6 @@ void SearchForHooks(SearchParam spUser) records.reset(); VirtualFree(trampolines, 0, MEM_RELEASE); for (int i = 0; i < CACHE_SIZE; ++i) signatureCache[i] = sumCache[i] = 0; - ConsoleOutput(HOOK_SEARCH_FINISHED, CACHE_SIZE - recordsAvailable); + ConsoleOutput(HOOK_SEARCH_FINISHED, sp.maxRecords - recordsAvailable); }).detach(); } diff --git a/texthook/main.h b/texthook/main.h index 6eebae4..af6c383 100644 --- a/texthook/main.h +++ b/texthook/main.h @@ -14,14 +14,7 @@ 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{ 0xcc, 0xcc, 0x48, 0x89 } : Array{ 0xcc, 0xcc, 0x55, 0x8b, 0xec }, sp.length = x64 ? 4 : 5); - sp.offset = 2; - sp.searchTime = 20000; - return sp; -}(); +inline SearchParam spDefault; extern "C" // minhook library {