diff --git a/GUI/CMakeLists.txt b/GUI/CMakeLists.txt index be99474..54eafae 100644 --- a/GUI/CMakeLists.txt +++ b/GUI/CMakeLists.txt @@ -1,11 +1,12 @@ include(QtUtils) msvc_registry_search() -find_qt5(Core Widgets) +find_qt5(Core Widgets WinExtras) add_executable(Textractor WIN32 main.cpp mainwindow.cpp extenwindow.cpp + attachprocessdialog.cpp host/exception.cpp host/host.cpp host/textthread.cpp @@ -14,7 +15,7 @@ add_executable(Textractor WIN32 Textractor.ico ) target_precompile_headers(Textractor REUSE_FROM pch) -target_link_libraries(Textractor Qt5::Widgets shell32 winhttp) +target_link_libraries(Textractor Qt5::Widgets Qt5::WinExtras shell32 winhttp) if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Core.dll AND NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Cored.dll) add_custom_command(TARGET Textractor diff --git a/GUI/attachprocessdialog.cpp b/GUI/attachprocessdialog.cpp new file mode 100644 index 0000000..830d9ed --- /dev/null +++ b/GUI/attachprocessdialog.cpp @@ -0,0 +1,48 @@ +#include "attachprocessdialog.h" +#include + +extern const char* SELECT_PROCESS; +extern const char* ATTACH_INFO; + +AttachProcessDialog::AttachProcessDialog(QWidget *parent, std::vector> processIcons) : + QDialog(parent, Qt::WindowCloseButtonHint), + model(this) +{ + ui.setupUi(this); + setWindowTitle(SELECT_PROCESS); + ui.label->setText(ATTACH_INFO); + ui.processIdEdit->setValidator(new QIntValidator(0, INT_MAX, this)); + ui.processList->setModel(&model); + + QPixmap transparent(100, 100); + transparent.fill(QColor::fromRgba(0)); + for (const auto& [process, icon] : processIcons) + { + auto item = new QStandardItem(icon ? QIcon(QtWin::fromHICON(icon)) : transparent, process); + item->setEditable(false); + model.appendRow(item); + } + + connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(ui.processList, &QListView::clicked, [this](QModelIndex index) + { + selectedProcess = model.item(index.row())->text(); + }); + connect(ui.processList, &QListView::doubleClicked, [this](QModelIndex index) + { + selectedProcess = model.item(index.row())->text(); + accept(); + }); + connect(ui.processIdEdit, &QLineEdit::returnPressed, [this] + { + selectedProcess = ui.processIdEdit->text(); + accept(); + }); + +} + +QString AttachProcessDialog::SelectedProcess() +{ + return selectedProcess.isEmpty() ? ui.processIdEdit->text() : selectedProcess; +} diff --git a/GUI/attachprocessdialog.h b/GUI/attachprocessdialog.h new file mode 100644 index 0000000..9d17eb6 --- /dev/null +++ b/GUI/attachprocessdialog.h @@ -0,0 +1,17 @@ +#pragma once + +#include "qtcommon.h" +#include "ui_attachprocessdialog.h" +#include + +class AttachProcessDialog : public QDialog +{ +public: + explicit AttachProcessDialog(QWidget *parent, std::vector> processIcons); + QString SelectedProcess(); + +private: + Ui::AttachProcessDialog ui; + QStandardItemModel model; + QString selectedProcess; +}; diff --git a/GUI/attachprocessdialog.ui b/GUI/attachprocessdialog.ui new file mode 100644 index 0000000..ece348a --- /dev/null +++ b/GUI/attachprocessdialog.ui @@ -0,0 +1,41 @@ + + + AttachProcessDialog + + + Qt::WindowModal + + + + 0 + 0 + 800 + 400 + + + + + + + + + + Process id + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/GUI/extenwindow.h b/GUI/extenwindow.h index 7561092..0cc3d40 100644 --- a/GUI/extenwindow.h +++ b/GUI/extenwindow.h @@ -2,11 +2,6 @@ #include "qtcommon.h" -namespace Ui -{ - class ExtenWindow; -} - struct InfoForExtension { const char* name; diff --git a/GUI/mainwindow.cpp b/GUI/mainwindow.cpp index e848db8..a8b9615 100644 --- a/GUI/mainwindow.cpp +++ b/GUI/mainwindow.cpp @@ -5,6 +5,7 @@ #include "extenwindow.h" #include "host/host.h" #include "host/hookcode.h" +#include "attachprocessdialog.h" #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include extern const char* ATTACH; extern const char* LAUNCH; @@ -28,7 +30,6 @@ extern const char* SETTINGS; extern const char* EXTENSIONS; extern const char* FONT; extern const char* SELECT_PROCESS; -extern const char* ATTACH_INFO; extern const char* SELECT_PROCESS_INFO; extern const char* FROM_COMPUTER; extern const char* PROCESSES; @@ -153,16 +154,31 @@ namespace void AttachProcess() { - QMultiHash allProcesses; + QMultiHash processesMap; + std::vector> processIcons; for (auto [processId, processName] : GetAllProcesses()) + { if (processName && (showSystemProcesses || processName->find(L":\\Windows\\") == std::string::npos)) - allProcesses.insert(QFileInfo(S(processName.value())).fileName(), processId); + { + QString fileName = QFileInfo(S(processName.value())).fileName(); + if (!processesMap.contains(fileName)) + { + HICON bigIcon, smallIcon; + ExtractIconExW(processName->c_str(), 0, &bigIcon, &smallIcon, 1); + processIcons.push_back({ fileName, bigIcon ? bigIcon : smallIcon }); + } + processesMap.insert(fileName, processId); + } + } + std::sort(processIcons.begin(), processIcons.end()); - QStringList processList(allProcesses.uniqueKeys()); - processList.sort(Qt::CaseInsensitive); - if (QString process = QInputDialog::getItem(This, SELECT_PROCESS, ATTACH_INFO, processList, 0, true, &ok, Qt::WindowCloseButtonHint); ok) - if (process.toInt(nullptr, 0)) Host::InjectProcess(process.toInt(nullptr, 0)); - else for (auto processId : allProcesses.values(process)) Host::InjectProcess(processId); + AttachProcessDialog attachProcessDialog(This, processIcons); + if (attachProcessDialog.exec()) + { + QString process = attachProcessDialog.SelectedProcess(); + if (int processId = process.toInt(nullptr, 0)) Host::InjectProcess(processId); + else for (int processId : processesMap.values(process)) Host::InjectProcess(processId); + } } void LaunchProcess() diff --git a/README.md b/README.md index 02ae573..0f94195 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Watch the [tutorial video](https://tinyurl.com/textractor-tutorial) for a quick ## Download Official stable releases of Textractor can be found [here](https://github.com/Artikash/Textractor/releases).
-Experimental builds of Textractor from the latest source can be found [here](https://ci.appveyor.com/project/Artikash/textractor/history) (in the 'Artifacts' section of a job).
The last release of ITHVNR can be found [here](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).
-Try running vcredist if you get an error when starting Textractor. +Experimental builds of Textractor (with debug info) from the latest source can be found [here](https://ci.appveyor.com/project/Artikash/textractor/history) in the 'Artifacts' section of each job.
+Try running vcredist if you get an error when starting Textractor or if nothing happens when you try attaching to a game. ## Features diff --git a/deploy.ps1 b/deploy.ps1 index 7355d7d..d1b1f91 100644 --- a/deploy.ps1 +++ b/deploy.ps1 @@ -46,6 +46,7 @@ foreach ($language in @{ "DeepL Translate", "DevTools DeepL Translate", "DevTools Papago Translate", + "DevTools Systran Translate", "Extra Newlines", "Extra Window", "Google Translate", @@ -66,7 +67,6 @@ foreach ($language in @{ &"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /v /t "http://timestamp.digicert.com" /fd SHA256 @(dir "$folder\**\*"); } - rm -Force -Recurse -Verbose "Runtime"; mkdir -Force -Verbose "Runtime"; foreach ($arch in @("x86", "x64")) @@ -79,6 +79,7 @@ foreach ($arch in @("x86", "x64")) "Qt5Gui.dll", "Qt5Network.dll", "Qt5WebSockets.dll", + "Qt5WinExtras.dll" "Qt5Widgets.dll", "platforms", "styles" diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 7c03d08..54a9c75 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(Copy\ to\ Clipboard MODULE copyclipboard.cpp extensionimpl.cpp) add_library(DeepL\ Translate MODULE deepltranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) add_library(DevTools\ DeepL\ Translate MODULE devtoolsdeepltranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) add_library(DevTools\ Papago\ Translate MODULE devtoolspapagotranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) +add_library(DevTools\ Systran\ Translate MODULE devtoolssystrantranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) add_library(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp) add_library(Extra\ Window MODULE extrawindow.cpp extensionimpl.cpp) add_library(Google\ Translate MODULE googletranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) @@ -26,6 +27,7 @@ target_precompile_headers(Copy\ to\ Clipboard REUSE_FROM pch) target_precompile_headers(DeepL\ Translate REUSE_FROM pch) target_precompile_headers(DevTools\ DeepL\ Translate REUSE_FROM pch) target_precompile_headers(DevTools\ Papago\ Translate REUSE_FROM pch) +target_precompile_headers(DevTools\ Systran\ Translate REUSE_FROM pch) target_precompile_headers(Extra\ Newlines REUSE_FROM pch) target_precompile_headers(Extra\ Window REUSE_FROM pch) target_precompile_headers(Google\ Translate REUSE_FROM pch) @@ -43,6 +45,7 @@ target_link_libraries(Bing\ Translate winhttp Qt5::Widgets) target_link_libraries(DeepL\ Translate winhttp Qt5::Widgets) target_link_libraries(DevTools\ DeepL\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets) target_link_libraries(DevTools\ Papago\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets) +target_link_libraries(DevTools\ Systran\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets) target_link_libraries(Extra\ Window Qt5::Widgets) target_link_libraries(Google\ Translate winhttp Qt5::Widgets) target_link_libraries(Lua lua53 Qt5::Widgets) @@ -59,10 +62,4 @@ if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5WebSockets.dll AND NOT EXISTS COMMAND set PATH=%PATH%$${qt5_install_prefix}/bin COMMAND Qt5::windeployqt --dir ${CMAKE_FINAL_OUTPUT_DIRECTORY} "${CMAKE_FINAL_OUTPUT_DIRECTORY}/DevTools\ DeepL\ Translate.dll" ) - add_custom_command(TARGET DevTools\ Papago\ Translate - POST_BUILD - COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt" - COMMAND set PATH=%PATH%$${qt5_install_prefix}/bin - COMMAND Qt5::windeployqt --dir ${CMAKE_FINAL_OUTPUT_DIRECTORY} "${CMAKE_FINAL_OUTPUT_DIRECTORY}/DevTools\ Papago\ Translate.dll" - ) endif() diff --git a/extensions/devtoolssystrantranslate.cpp b/extensions/devtoolssystrantranslate.cpp new file mode 100644 index 0000000..6f158c4 --- /dev/null +++ b/extensions/devtoolssystrantranslate.cpp @@ -0,0 +1,63 @@ +#include "qtcommon.h" +#include "devtools.h" + +extern const wchar_t* ERROR_START_CHROME; +extern const wchar_t* TRANSLATION_ERROR; + +extern Synchronized translateTo, translateFrom; +extern QFormLayout* display; +extern Settings settings; + +const char* TRANSLATION_PROVIDER = "DevTools Systran Translate"; +const char* GET_API_KEY_FROM = nullptr; +bool translateSelectedOnly = true, rateLimitAll = false, rateLimitSelected = false, useCache = true, useFilter = true; +int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 2500; + +QStringList languages +{ + "Chinese: zh", + "Chinese (traditional): zt", + "English: en", + "French: fr", + "German: de", + "Italian: it", + "Japanese: ja", + "Korean: ko", + "Portuguese: pt", + "Spanish: es", + "Thai: th", +}; +std::wstring autoDetectLanguage = L"auto"; + +BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + { + DevTools::Start(); + } + break; + case DLL_PROCESS_DETACH: + { + DevTools::Close(); + } + break; + } + return TRUE; +} + +std::pair Translate(const std::wstring& text) +{ + if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) }; + // DevTools can't handle concurrent translations yet + static std::mutex translationMutex; + std::scoped_lock lock(translationMutex); + + DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://translate.systran.net/?&source=%s&target=%s&input=%s"})", translateFrom.Copy(), translateTo.Copy(), Escape(text))); + for (int retry = 0; ++retry < 100; Sleep(100)) + if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate", + LR"({"expression":"document.querySelector('#outputEditor').textContent.trim() ","returnByValue":true})" + )[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() }; + return { false, TRANSLATION_ERROR }; +}