diff --git a/GUI/extenwindow.cpp b/GUI/extenwindow.cpp index b36fe12..eee5402 100644 --- a/GUI/extenwindow.cpp +++ b/GUI/extenwindow.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -30,14 +29,12 @@ namespace { if (extenName == ITH_DLL) return; // Extension is dll and exports "OnNewSentence" - HMODULE module = GetModuleHandleW(S(extenName).c_str()); - if (!module) module = LoadLibraryW(S(extenName).c_str()); - if (!module) return; - FARPROC callback = GetProcAddress(module, "OnNewSentence"); - if (!callback) return; - std::scoped_lock writeLock(extenMutex); - extensions[extenName] = (wchar_t*(*)(const wchar_t*, const InfoForExtension*))callback; - extenNames.push_back(extenName); + if (FARPROC callback = GetProcAddress(LoadLibraryOnce(S(extenName)), "OnNewSentence")) + { + std::scoped_lock writeLock(extenMutex); + extensions[extenName] = (wchar_t*(*)(const wchar_t*, const InfoForExtension*))callback; + extenNames.push_back(extenName); + } } void Unload(QString extenName) diff --git a/GUI/main.cpp b/GUI/main.cpp index cd9b31a..5dbebb2 100644 --- a/GUI/main.cpp +++ b/GUI/main.cpp @@ -2,7 +2,6 @@ #include "misc.h" #include "host/util.h" #include -#include int main(int argc, char *argv[]) { diff --git a/GUI/mainwindow.cpp b/GUI/mainwindow.cpp index 0717b90..8c54689 100644 --- a/GUI/mainwindow.cpp +++ b/GUI/mainwindow.cpp @@ -11,6 +11,7 @@ #include #include #include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), @@ -20,6 +21,7 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); for (auto[text, slot] : Array>{ { ATTACH, &MainWindow::AttachProcess }, + { LAUNCH, &MainWindow::LaunchProcess }, { DETACH, &MainWindow::DetachProcess }, { ADD_HOOK, &MainWindow::AddHook }, { SAVE_HOOKS, &MainWindow::SaveHooks }, @@ -90,6 +92,9 @@ void MainWindow::ProcessConnected(DWORD processId) { QString process = S(Util::GetModuleFilename(processId).value_or(L"???")); ui->processCombo->addItem(QString::number(processId, 16).toUpper() + ": " + QFileInfo(process).fileName()); + if (process == "???") return; + + QTextFile(GAME_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Append).write((process + "\n").toUtf8()); QStringList allProcesses = QString(QTextFile(HOOK_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts); // Can't use QFileInfo::absoluteFilePath since hook save file has '\\' as path separator @@ -198,13 +203,53 @@ void MainWindow::AttachProcess() QStringList processList(allProcesses.uniqueKeys()); processList.sort(Qt::CaseInsensitive); - bool ok; QString process = QInputDialog::getItem(this, SELECT_PROCESS, ATTACH_INFO, processList, 0, true, &ok, Qt::WindowCloseButtonHint); if (!ok) return; if (process.toInt(nullptr, 0)) Host::InjectProcess(process.toInt(nullptr, 0)); else for (auto processId : allProcesses.values(process)) Host::InjectProcess(processId); } +void MainWindow::LaunchProcess() +{ + QStringList savedProcesses = QString::fromUtf8(QTextFile(GAME_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts); + savedProcesses.removeDuplicates(); + savedProcesses.sort(Qt::CaseInsensitive); + savedProcesses.push_back(SEARCH_GAME); + std::wstring process = S(QInputDialog::getItem(this, SELECT_PROCESS, "", savedProcesses, 0, true, &ok, Qt::WindowCloseButtonHint)); + if (!ok) return; + if (S(process) == SEARCH_GAME) process = S(QDir::toNativeSeparators(QFileDialog::getOpenFileName(this, SELECT_PROCESS, "C:\\", PROCESSES))); + if (process.empty()) return; + std::wstring path = std::wstring(process).erase(process.rfind(L'\\')); + PROCESS_INFORMATION info = {}; + if (HMODULE localeEmulator = LoadLibraryOnce(L"LoaderDll")) + { + // see https://github.com/xupefei/Locale-Emulator/blob/aa99dec3b25708e676c90acf5fed9beaac319160/LEProc/LoaderWrapper.cs#L252 + struct + { + ULONG AnsiCodePage = Host::defaultCodepage; + ULONG OemCodePage = Host::defaultCodepage; + ULONG LocaleID = LANG_JAPANESE; + ULONG DefaultCharset = DEFAULT_CHARSET; + ULONG HookUiLanguageApi = FALSE; + WCHAR DefaultFaceName[LF_FACESIZE] = {}; + TIME_ZONE_INFORMATION Timezone; + ULONG64 Unused = 0; + } LEB; + GetTimeZoneInformation(&LEB.Timezone); + ((LONG(__stdcall*)(decltype(&LEB), LPCWSTR appName, LPWSTR commandLine, LPCWSTR currentDir, void*, void*, PROCESS_INFORMATION*, void*, void*, void*, void*)) + GetProcAddress(localeEmulator, "LeCreateProcess"))(&LEB, process.c_str(), NULL, path.c_str(), NULL, NULL, &info, NULL, NULL, NULL, NULL); + } + if (info.hProcess == NULL) + { + STARTUPINFOW DUMMY = { sizeof(DUMMY) }; + CreateProcessW(process.c_str(), NULL, nullptr, nullptr, FALSE, 0, nullptr, path.c_str(), &DUMMY, &info); + } + if (info.hProcess == NULL) return Host::AddConsoleOutput(LAUNCH_FAILED); + Host::InjectProcess(info.dwProcessId); + CloseHandle(info.hProcess); + CloseHandle(info.hThread); +} + void MainWindow::DetachProcess() { Host::DetachProcess(GetSelectedProcessId()); @@ -212,7 +257,6 @@ void MainWindow::DetachProcess() void MainWindow::AddHook() { - bool ok; QString hookCode = QInputDialog::getText(this, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, "", &ok, Qt::WindowCloseButtonHint); if (!ok) return; if (auto hp = ParseCode(hookCode)) Host::InsertHook(GetSelectedProcessId(), hp.value()); @@ -233,7 +277,7 @@ void MainWindow::SaveHooks() if (!(hp.type & HOOK_ENGINE)) hookCodes[tp.addr] = GenerateCode(hp, tp.processId); } } - QTextFile(HOOK_SAVE_FILE, QIODevice::Append).write((S(processName.value()) + " , " + QStringList(hookCodes.values()).join(" , ") + "\n").toUtf8()); + QTextFile(HOOK_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Append).write((S(processName.value()) + " , " + QStringList(hookCodes.values()).join(" , ") + "\n").toUtf8()); } } diff --git a/GUI/mainwindow.h b/GUI/mainwindow.h index 87a3017..97080a9 100644 --- a/GUI/mainwindow.h +++ b/GUI/mainwindow.h @@ -15,6 +15,8 @@ public: ~MainWindow(); private: + inline static thread_local bool ok = false; + void closeEvent(QCloseEvent*) override; void ProcessConnected(DWORD processId); void ProcessDisconnected(DWORD processId); @@ -26,6 +28,7 @@ private: DWORD GetSelectedProcessId(); std::unordered_map GetMiscInfo(TextThread* thread); void AttachProcess(); + void LaunchProcess(); void DetachProcess(); void AddHook(); void SaveHooks(); diff --git a/GUI/misc.cpp b/GUI/misc.cpp index 2d8a280..b508f4d 100644 --- a/GUI/misc.cpp +++ b/GUI/misc.cpp @@ -265,6 +265,13 @@ QString GenerateCode(HookParam hp, DWORD processId) else return GenerateHCode(hp, processId); } +HMODULE LoadLibraryOnce(std::wstring fileName) +{ + HMODULE module = GetModuleHandleW(fileName.c_str()); + if (!module) module = LoadLibraryW(fileName.c_str()); + return module; +} + TEST( assert(ParseCode("/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")), assert(ParseCode("HB4@0")), diff --git a/GUI/misc.h b/GUI/misc.h index d07407d..8b48496 100644 --- a/GUI/misc.h +++ b/GUI/misc.h @@ -13,3 +13,4 @@ inline std::wstring S(const QString& S) { return { S.toStdWString() }; } inline QString S(const std::wstring& S) { return QString::fromStdWString(S); } std::optional ParseCode(QString HCode); QString GenerateCode(HookParam hp, DWORD processId); +HMODULE LoadLibraryOnce(std::wstring fileName); diff --git a/GUI/qtcommon.h b/GUI/qtcommon.h index 89cee8e..91d810c 100644 --- a/GUI/qtcommon.h +++ b/GUI/qtcommon.h @@ -8,5 +8,6 @@ #include #include #include +#include #include #include diff --git a/include/defs.h b/include/defs.h index 22654e0..0adfe7d 100644 --- a/include/defs.h +++ b/include/defs.h @@ -23,6 +23,7 @@ constexpr auto ITH_HOOKMAN_MUTEX_ = L"VNR_HOOKMAN_"; // ITH_HOOKMAN_%d constexpr auto ITH_DLL = L"vnrhook"; // .dll but LoadLibrary automatically adds that constexpr auto CONFIG_FILE = u8"Textractor.ini"; constexpr auto HOOK_SAVE_FILE = u8"SavedHooks.txt"; +constexpr auto GAME_SAVE_FILE = u8"Games.txt"; constexpr auto EXTEN_SAVE_FILE = u8"Extensions.txt"; // Functions diff --git a/include/text.h b/include/text.h index 548ad8d..0fe95cb 100644 --- a/include/text.h +++ b/include/text.h @@ -5,6 +5,7 @@ #ifdef ENGLISH constexpr auto ATTACH = u8"Attach to game"; +constexpr auto LAUNCH = u8"Launch game"; constexpr auto DETACH = u8"Detach from game"; constexpr auto ADD_HOOK = u8"Add hook"; constexpr auto SAVE_HOOKS = u8"Save hook(s)"; @@ -13,6 +14,8 @@ constexpr auto EXTENSIONS = u8"Extensions"; constexpr auto SELECT_PROCESS = u8"Select Process"; constexpr auto ATTACH_INFO = u8R"(If you don't see the process you want to attach, try running with admin rights You can also type in the process id)"; +constexpr auto SEARCH_GAME = u8"Select from computer"; +constexpr auto PROCESSES = u8"Processes (*.exe)"; constexpr auto CODE_INFODUMP = u8R"(Search for text S[codepage#]text OR @@ -49,6 +52,7 @@ constexpr auto UPDATE_AVAILABLE = L"Update available: download it from https://g constexpr auto ALREADY_INJECTED = L"Textractor: already injected"; constexpr auto ARCHITECTURE_MISMATCH = L"Textractor: architecture mismatch: try 32 bit Textractor instead"; constexpr auto INJECT_FAILED = L"Textractor: couldn't inject"; +constexpr auto LAUNCH_FAILED = L"Textractor: couldn't launch"; constexpr auto INVALID_CODE = L"Textractor: invalid code"; constexpr auto INVALID_CODEPAGE = L"Textractor: couldn't convert text (invalid codepage?)"; constexpr auto PIPE_CONNECTED = u8"Textractor: pipe connected"; diff --git a/release/LoaderDll.dll b/release/LoaderDll.dll new file mode 100644 index 0000000..f39bbee Binary files /dev/null and b/release/LoaderDll.dll differ diff --git a/release/LocaleEmulator.dll b/release/LocaleEmulator.dll new file mode 100644 index 0000000..f13ef26 Binary files /dev/null and b/release/LocaleEmulator.dll differ