save attached games and allow launching from textractor using locale emulator
This commit is contained in:
parent
8e80543e2e
commit
75454b3fa5
@ -7,7 +7,6 @@
|
|||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
#include <QDragEnterEvent>
|
#include <QDragEnterEvent>
|
||||||
#include <QDropEvent>
|
#include <QDropEvent>
|
||||||
#include <QFileDialog>
|
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
@ -30,14 +29,12 @@ namespace
|
|||||||
{
|
{
|
||||||
if (extenName == ITH_DLL) return;
|
if (extenName == ITH_DLL) return;
|
||||||
// Extension is dll and exports "OnNewSentence"
|
// Extension is dll and exports "OnNewSentence"
|
||||||
HMODULE module = GetModuleHandleW(S(extenName).c_str());
|
if (FARPROC callback = GetProcAddress(LoadLibraryOnce(S(extenName)), "OnNewSentence"))
|
||||||
if (!module) module = LoadLibraryW(S(extenName).c_str());
|
{
|
||||||
if (!module) return;
|
std::scoped_lock writeLock(extenMutex);
|
||||||
FARPROC callback = GetProcAddress(module, "OnNewSentence");
|
extensions[extenName] = (wchar_t*(*)(const wchar_t*, const InfoForExtension*))callback;
|
||||||
if (!callback) return;
|
extenNames.push_back(extenName);
|
||||||
std::scoped_lock writeLock(extenMutex);
|
}
|
||||||
extensions[extenName] = (wchar_t*(*)(const wchar_t*, const InfoForExtension*))callback;
|
|
||||||
extenNames.push_back(extenName);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Unload(QString extenName)
|
void Unload(QString extenName)
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
#include "host/util.h"
|
#include "host/util.h"
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDir>
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QSpinBox>
|
#include <QSpinBox>
|
||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent) :
|
MainWindow::MainWindow(QWidget *parent) :
|
||||||
QMainWindow(parent),
|
QMainWindow(parent),
|
||||||
@ -20,6 +21,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||||||
ui->setupUi(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 },
|
{ ATTACH, &MainWindow::AttachProcess },
|
||||||
|
{ LAUNCH, &MainWindow::LaunchProcess },
|
||||||
{ DETACH, &MainWindow::DetachProcess },
|
{ DETACH, &MainWindow::DetachProcess },
|
||||||
{ ADD_HOOK, &MainWindow::AddHook },
|
{ ADD_HOOK, &MainWindow::AddHook },
|
||||||
{ SAVE_HOOKS, &MainWindow::SaveHooks },
|
{ SAVE_HOOKS, &MainWindow::SaveHooks },
|
||||||
@ -90,6 +92,9 @@ void MainWindow::ProcessConnected(DWORD processId)
|
|||||||
{
|
{
|
||||||
QString process = S(Util::GetModuleFilename(processId).value_or(L"???"));
|
QString process = S(Util::GetModuleFilename(processId).value_or(L"???"));
|
||||||
ui->processCombo->addItem(QString::number(processId, 16).toUpper() + ": " + QFileInfo(process).fileName());
|
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);
|
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
|
// Can't use QFileInfo::absoluteFilePath since hook save file has '\\' as path separator
|
||||||
@ -198,13 +203,53 @@ void MainWindow::AttachProcess()
|
|||||||
|
|
||||||
QStringList processList(allProcesses.uniqueKeys());
|
QStringList processList(allProcesses.uniqueKeys());
|
||||||
processList.sort(Qt::CaseInsensitive);
|
processList.sort(Qt::CaseInsensitive);
|
||||||
bool ok;
|
|
||||||
QString process = QInputDialog::getItem(this, SELECT_PROCESS, ATTACH_INFO, processList, 0, true, &ok, Qt::WindowCloseButtonHint);
|
QString process = QInputDialog::getItem(this, SELECT_PROCESS, ATTACH_INFO, processList, 0, true, &ok, Qt::WindowCloseButtonHint);
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
if (process.toInt(nullptr, 0)) Host::InjectProcess(process.toInt(nullptr, 0));
|
if (process.toInt(nullptr, 0)) Host::InjectProcess(process.toInt(nullptr, 0));
|
||||||
else for (auto processId : allProcesses.values(process)) Host::InjectProcess(processId);
|
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()
|
void MainWindow::DetachProcess()
|
||||||
{
|
{
|
||||||
Host::DetachProcess(GetSelectedProcessId());
|
Host::DetachProcess(GetSelectedProcessId());
|
||||||
@ -212,7 +257,6 @@ void MainWindow::DetachProcess()
|
|||||||
|
|
||||||
void MainWindow::AddHook()
|
void MainWindow::AddHook()
|
||||||
{
|
{
|
||||||
bool ok;
|
|
||||||
QString hookCode = QInputDialog::getText(this, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, "", &ok, Qt::WindowCloseButtonHint);
|
QString hookCode = QInputDialog::getText(this, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, "", &ok, Qt::WindowCloseButtonHint);
|
||||||
if (!ok) return;
|
if (!ok) return;
|
||||||
if (auto hp = ParseCode(hookCode)) Host::InsertHook(GetSelectedProcessId(), hp.value());
|
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);
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ public:
|
|||||||
~MainWindow();
|
~MainWindow();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
inline static thread_local bool ok = false;
|
||||||
|
|
||||||
void closeEvent(QCloseEvent*) override;
|
void closeEvent(QCloseEvent*) override;
|
||||||
void ProcessConnected(DWORD processId);
|
void ProcessConnected(DWORD processId);
|
||||||
void ProcessDisconnected(DWORD processId);
|
void ProcessDisconnected(DWORD processId);
|
||||||
@ -26,6 +28,7 @@ private:
|
|||||||
DWORD GetSelectedProcessId();
|
DWORD GetSelectedProcessId();
|
||||||
std::unordered_map<const char*, int64_t> GetMiscInfo(TextThread* thread);
|
std::unordered_map<const char*, int64_t> GetMiscInfo(TextThread* thread);
|
||||||
void AttachProcess();
|
void AttachProcess();
|
||||||
|
void LaunchProcess();
|
||||||
void DetachProcess();
|
void DetachProcess();
|
||||||
void AddHook();
|
void AddHook();
|
||||||
void SaveHooks();
|
void SaveHooks();
|
||||||
|
@ -265,6 +265,13 @@ QString GenerateCode(HookParam hp, DWORD processId)
|
|||||||
else return GenerateHCode(hp, 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(
|
TEST(
|
||||||
assert(ParseCode("/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")),
|
assert(ParseCode("/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")),
|
||||||
assert(ParseCode("HB4@0")),
|
assert(ParseCode("HB4@0")),
|
||||||
|
@ -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); }
|
inline QString S(const std::wstring& S) { return QString::fromStdWString(S); }
|
||||||
std::optional<HookParam> ParseCode(QString HCode);
|
std::optional<HookParam> ParseCode(QString HCode);
|
||||||
QString GenerateCode(HookParam hp, DWORD processId);
|
QString GenerateCode(HookParam hp, DWORD processId);
|
||||||
|
HMODULE LoadLibraryOnce(std::wstring fileName);
|
||||||
|
@ -8,5 +8,6 @@
|
|||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QDir>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
@ -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 ITH_DLL = L"vnrhook"; // .dll but LoadLibrary automatically adds that
|
||||||
constexpr auto CONFIG_FILE = u8"Textractor.ini";
|
constexpr auto CONFIG_FILE = u8"Textractor.ini";
|
||||||
constexpr auto HOOK_SAVE_FILE = u8"SavedHooks.txt";
|
constexpr auto HOOK_SAVE_FILE = u8"SavedHooks.txt";
|
||||||
|
constexpr auto GAME_SAVE_FILE = u8"Games.txt";
|
||||||
constexpr auto EXTEN_SAVE_FILE = u8"Extensions.txt";
|
constexpr auto EXTEN_SAVE_FILE = u8"Extensions.txt";
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#ifdef ENGLISH
|
#ifdef ENGLISH
|
||||||
constexpr auto ATTACH = u8"Attach to game";
|
constexpr auto ATTACH = u8"Attach to game";
|
||||||
|
constexpr auto LAUNCH = u8"Launch game";
|
||||||
constexpr auto DETACH = u8"Detach from game";
|
constexpr auto DETACH = u8"Detach from game";
|
||||||
constexpr auto ADD_HOOK = u8"Add hook";
|
constexpr auto ADD_HOOK = u8"Add hook";
|
||||||
constexpr auto SAVE_HOOKS = u8"Save hook(s)";
|
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 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
|
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)";
|
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
|
constexpr auto CODE_INFODUMP = u8R"(Search for text
|
||||||
S[codepage#]text
|
S[codepage#]text
|
||||||
OR
|
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 ALREADY_INJECTED = L"Textractor: already injected";
|
||||||
constexpr auto ARCHITECTURE_MISMATCH = L"Textractor: architecture mismatch: try 32 bit Textractor instead";
|
constexpr auto ARCHITECTURE_MISMATCH = L"Textractor: architecture mismatch: try 32 bit Textractor instead";
|
||||||
constexpr auto INJECT_FAILED = L"Textractor: couldn't inject";
|
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_CODE = L"Textractor: invalid code";
|
||||||
constexpr auto INVALID_CODEPAGE = L"Textractor: couldn't convert text (invalid codepage?)";
|
constexpr auto INVALID_CODEPAGE = L"Textractor: couldn't convert text (invalid codepage?)";
|
||||||
constexpr auto PIPE_CONNECTED = u8"Textractor: pipe connected";
|
constexpr auto PIPE_CONNECTED = u8"Textractor: pipe connected";
|
||||||
|
BIN
release/LoaderDll.dll
Normal file
BIN
release/LoaderDll.dll
Normal file
Binary file not shown.
BIN
release/LocaleEmulator.dll
Normal file
BIN
release/LocaleEmulator.dll
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user