massive refactor, also fix newline issue and google translate throw and different number formats for process id
This commit is contained in:
parent
3a34f989e5
commit
fc81b17a3c
@ -14,10 +14,10 @@ add_executable(Textractor WIN32
|
|||||||
Textractor.ico
|
Textractor.ico
|
||||||
)
|
)
|
||||||
target_precompile_headers(Textractor REUSE_FROM pch)
|
target_precompile_headers(Textractor REUSE_FROM pch)
|
||||||
target_link_libraries(${PROJECT_NAME} Qt5::Widgets shell32 winhttp)
|
target_link_libraries(Textractor Qt5::Widgets shell32 winhttp)
|
||||||
|
|
||||||
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Core.dll)
|
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Core.dll)
|
||||||
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Cored.dll)
|
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Cored.dll)
|
||||||
install_qt5_libs(${PROJECT_NAME})
|
install_qt5_libs(Textractor)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
@ -138,8 +138,6 @@ ExtenWindow::ExtenWindow(QWidget* parent) :
|
|||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtenWindow::~ExtenWindow() = default;
|
|
||||||
|
|
||||||
bool ExtenWindow::eventFilter(QObject* target, QEvent* event)
|
bool ExtenWindow::eventFilter(QObject* target, QEvent* event)
|
||||||
{
|
{
|
||||||
// See https://stackoverflow.com/questions/1224432/how-do-i-respond-to-an-internal-drag-and-drop-operation-using-a-qlistwidget/1528215
|
// See https://stackoverflow.com/questions/1224432/how-do-i-respond-to-an-internal-drag-and-drop-operation-using-a-qlistwidget/1528215
|
||||||
|
@ -20,7 +20,6 @@ class ExtenWindow : public QMainWindow
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ExtenWindow(QWidget* parent = nullptr);
|
explicit ExtenWindow(QWidget* parent = nullptr);
|
||||||
~ExtenWindow();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool eventFilter(QObject* target, QEvent* event) override;
|
bool eventFilter(QObject* target, QEvent* event) override;
|
||||||
|
@ -129,8 +129,7 @@ namespace
|
|||||||
{
|
{
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
int value = 0;
|
int value = 0;
|
||||||
try { value = std::stoi(HCode, &size, 16); }
|
try { value = std::stoi(HCode, &size, 16); } catch (std::invalid_argument) {}
|
||||||
catch (std::invalid_argument) {}
|
|
||||||
HCode.erase(0, size);
|
HCode.erase(0, size);
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
@ -32,8 +32,7 @@ namespace
|
|||||||
{
|
{
|
||||||
if (!view) return {};
|
if (!view) return {};
|
||||||
std::scoped_lock lock(viewMutex);
|
std::scoped_lock lock(viewMutex);
|
||||||
for (auto hook : view)
|
for (auto hook : view) if (hook.address == addr) return hook;
|
||||||
if (hook.address == addr) return hook;
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,8 +68,7 @@ namespace
|
|||||||
void RemoveThreads(std::function<bool(ThreadParam)> removeIf)
|
void RemoveThreads(std::function<bool(ThreadParam)> removeIf)
|
||||||
{
|
{
|
||||||
std::vector<TextThread*> threadsToRemove;
|
std::vector<TextThread*> threadsToRemove;
|
||||||
for (auto& [tp, thread] : textThreadsByParams.Acquire().contents)
|
for (auto& [tp, thread] : textThreadsByParams.Acquire().contents) if (removeIf(tp)) threadsToRemove.push_back(&thread);
|
||||||
if (removeIf(tp)) threadsToRemove.push_back(&thread);
|
|
||||||
for (auto thread : threadsToRemove)
|
for (auto thread : threadsToRemove)
|
||||||
{
|
{
|
||||||
OnDestroy(*thread);
|
OnDestroy(*thread);
|
||||||
@ -250,8 +248,7 @@ namespace Host
|
|||||||
|
|
||||||
TextThread* GetThread(int64_t handle)
|
TextThread* GetThread(int64_t handle)
|
||||||
{
|
{
|
||||||
for (auto& [tp, thread] : textThreadsByParams.Acquire().contents)
|
for (auto& [tp, thread] : textThreadsByParams.Acquire().contents) if (thread.handle == handle) return &thread;
|
||||||
if (thread.handle == handle) return &thread;
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ TextThread::TextThread(ThreadParam tp, HookParam hp, std::optional<std::wstring>
|
|||||||
|
|
||||||
void TextThread::Start()
|
void TextThread::Start()
|
||||||
{
|
{
|
||||||
CreateTimerQueueTimer(&timer, NULL, [](void* This, BOOLEAN) { ((TextThread*)This)->Flush(); }, this, 10, 10, WT_EXECUTELONGFUNCTION);
|
CreateTimerQueueTimer(&timer, NULL, [](void* This, auto) { ((TextThread*)This)->Flush(); }, this, 10, 10, WT_EXECUTELONGFUNCTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextThread::Stop()
|
void TextThread::Stop()
|
||||||
@ -42,8 +42,21 @@ void TextThread::Push(BYTE* data, int length)
|
|||||||
|
|
||||||
BYTE doubleByteChar[2];
|
BYTE doubleByteChar[2];
|
||||||
if (length == 1) // doublebyte characters must be processed as pairs
|
if (length == 1) // doublebyte characters must be processed as pairs
|
||||||
if (leadByte) std::tie(doubleByteChar[0], doubleByteChar[1], data, length, leadByte) = std::tuple(leadByte, data[0], doubleByteChar, 2, 0);
|
{
|
||||||
else if (IsDBCSLeadByteEx(hp.codepage ? hp.codepage : Host::defaultCodepage, data[0])) std::tie(leadByte, length) = std::tuple(data[0], 0);
|
if (leadByte)
|
||||||
|
{
|
||||||
|
doubleByteChar[0] = leadByte;
|
||||||
|
doubleByteChar[1] = data[0];
|
||||||
|
data = doubleByteChar;
|
||||||
|
length = 2;
|
||||||
|
leadByte = 0;
|
||||||
|
}
|
||||||
|
else if (IsDBCSLeadByteEx(hp.codepage ? hp.codepage : Host::defaultCodepage, data[0]))
|
||||||
|
{
|
||||||
|
leadByte = data[0];
|
||||||
|
length = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hp.type & HEX_DUMP) for (int i = 0; i < length; i += sizeof(short)) buffer.append(FormatString(L"%04hX ", *(short*)(data + i)));
|
if (hp.type & HEX_DUMP) for (int i = 0; i < length; i += sizeof(short)) buffer.append(FormatString(L"%04hX ", *(short*)(data + i)));
|
||||||
else if (hp.type & USING_UNICODE) buffer.append((wchar_t*)data, length / sizeof(wchar_t));
|
else if (hp.type & USING_UNICODE) buffer.append((wchar_t*)data, length / sizeof(wchar_t));
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
extern const char* ATTACH;
|
extern const char* ATTACH;
|
||||||
extern const char* LAUNCH;
|
extern const char* LAUNCH;
|
||||||
extern const char* GAME_CONFIG;
|
extern const char* CONFIG;
|
||||||
extern const char* DETACH;
|
extern const char* DETACH;
|
||||||
extern const char* FORGET;
|
extern const char* FORGET;
|
||||||
extern const char* ADD_HOOK;
|
extern const char* ADD_HOOK;
|
||||||
@ -198,14 +198,14 @@ namespace
|
|||||||
CloseHandle(info.hThread);
|
CloseHandle(info.hThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpenProcessConfig()
|
void ConfigureProcess()
|
||||||
{
|
{
|
||||||
if (auto processName = GetModuleFilename(selectedProcessId)) if (int last = processName->rfind(L'\\') + 1)
|
if (auto processName = GetModuleFilename(selectedProcessId)) if (int last = processName->rfind(L'\\') + 1)
|
||||||
{
|
{
|
||||||
std::wstring configFile = std::wstring(processName.value()).replace(last, std::string::npos, GAME_CONFIG_FILE);
|
std::wstring configFile = std::wstring(processName.value()).replace(last, std::string::npos, GAME_CONFIG_FILE);
|
||||||
if (!std::filesystem::exists(configFile)) QTextFile(S(configFile), QFile::WriteOnly).write("see https://github.com/Artikash/Textractor/wiki/Game-configuration-file");
|
if (!std::filesystem::exists(configFile)) QTextFile(S(configFile), QFile::WriteOnly).write("see https://github.com/Artikash/Textractor/wiki/Game-configuration-file");
|
||||||
if (std::filesystem::exists(configFile)) _wspawnlp(_P_DETACH, L"notepad", L"notepad", configFile.c_str(), NULL);
|
if (std::filesystem::exists(configFile)) _wspawnlp(_P_DETACH, L"notepad", L"notepad", configFile.c_str(), NULL);
|
||||||
else QMessageBox::critical(This, GAME_CONFIG, QString(FAILED_TO_CREATE_CONFIG_FILE).arg(S(configFile)));
|
else QMessageBox::critical(This, CONFIG, QString(FAILED_TO_CREATE_CONFIG_FILE).arg(S(configFile)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,8 +258,7 @@ namespace
|
|||||||
hookList->setAttribute(Qt::WA_DeleteOnClose);
|
hookList->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
hookList->setMinimumSize({ 300, 50 });
|
hookList->setMinimumSize({ 300, 50 });
|
||||||
hookList->setWindowTitle(DOUBLE_CLICK_TO_REMOVE_HOOK);
|
hookList->setWindowTitle(DOUBLE_CLICK_TO_REMOVE_HOOK);
|
||||||
for (auto [address, hp] : hooks)
|
for (auto [address, hp] : hooks) new QListWidgetItem(QString(hp.name) + "@" + QString::number(address, 16), hookList);
|
||||||
new QListWidgetItem(QString(hp.name) + "@" + QString::number(address, 16), hookList);
|
|
||||||
QObject::connect(hookList, &QListWidget::itemDoubleClicked, [processId, hookList](QListWidgetItem* item)
|
QObject::connect(hookList, &QListWidget::itemDoubleClicked, [processId, hookList](QListWidgetItem* item)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -335,8 +334,7 @@ namespace
|
|||||||
layout.addRow(&confirm);
|
layout.addRow(&confirm);
|
||||||
if (!dialog.exec()) return;
|
if (!dialog.exec()) return;
|
||||||
wcsncpy_s(sp.text, S(textInput.text()).c_str(), PATTERN_SIZE - 1);
|
wcsncpy_s(sp.text, S(textInput.text()).c_str(), PATTERN_SIZE - 1);
|
||||||
try { Host::FindHooks(selectedProcessId, sp); }
|
try { Host::FindHooks(selectedProcessId, sp); } catch (std::out_of_range) {}
|
||||||
catch (std::out_of_range) {}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,8 +406,7 @@ namespace
|
|||||||
catch (std::out_of_range) { return; }
|
catch (std::out_of_range) { return; }
|
||||||
std::thread([hooks]
|
std::thread([hooks]
|
||||||
{
|
{
|
||||||
for (int lastSize = 0; hooks->size() == 0 || hooks->size() != lastSize; Sleep(2000))
|
for (int lastSize = 0; hooks->size() == 0 || hooks->size() != lastSize; Sleep(2000)) lastSize = hooks->size();
|
||||||
lastSize = hooks->size();
|
|
||||||
|
|
||||||
QString saveFileName;
|
QString saveFileName;
|
||||||
QMetaObject::invokeMethod(This, [&]
|
QMetaObject::invokeMethod(This, [&]
|
||||||
@ -562,6 +559,7 @@ namespace
|
|||||||
|
|
||||||
bool SentenceReceived(TextThread& thread, std::wstring& sentence)
|
bool SentenceReceived(TextThread& thread, std::wstring& sentence)
|
||||||
{
|
{
|
||||||
|
for (int i = 0; i < sentence.size(); ++i) if (sentence[i] == '\r' && sentence[i + 1] == '\n') sentence[i] = 0x200b; // for some reason \r appears as newline - no need to double
|
||||||
if (!DispatchSentenceToExtensions(sentence, GetSentenceInfo(thread).data())) return false;
|
if (!DispatchSentenceToExtensions(sentence, GetSentenceInfo(thread).data())) return false;
|
||||||
sentence += L'\n';
|
sentence += L'\n';
|
||||||
if (&thread == current) QMetaObject::invokeMethod(This, [sentence = S(sentence)]() mutable
|
if (&thread == current) QMetaObject::invokeMethod(This, [sentence = S(sentence)]() mutable
|
||||||
@ -598,7 +596,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
for (auto [text, slot] : Array<const char*, void(&)()>{
|
for (auto [text, slot] : Array<const char*, void(&)()>{
|
||||||
{ ATTACH, AttachProcess },
|
{ ATTACH, AttachProcess },
|
||||||
{ LAUNCH, LaunchProcess },
|
{ LAUNCH, LaunchProcess },
|
||||||
{ GAME_CONFIG, OpenProcessConfig },
|
{ CONFIG, ConfigureProcess },
|
||||||
{ DETACH, DetachProcess },
|
{ DETACH, DetachProcess },
|
||||||
{ FORGET, ForgetProcess },
|
{ FORGET, ForgetProcess },
|
||||||
{ ADD_HOOK, AddHook },
|
{ ADD_HOOK, AddHook },
|
||||||
@ -647,7 +645,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
|||||||
for (int i = 0; i < argc; ++i)
|
for (int i = 0; i < argc; ++i)
|
||||||
if (std::wstring arg = argv[i]; arg[0] == L'/' || arg[0] == L'-')
|
if (std::wstring arg = argv[i]; arg[0] == L'/' || arg[0] == L'-')
|
||||||
if (arg[1] == L'p' || arg[1] == L'P')
|
if (arg[1] == L'p' || arg[1] == L'P')
|
||||||
if (DWORD processId = _wtoi(arg.substr(2).c_str())) Host::InjectProcess(processId);
|
if (DWORD processId = wcstoul(arg.substr(2).c_str(), nullptr, 0)) Host::InjectProcess(processId);
|
||||||
else for (auto [processId, processName] : processes)
|
else for (auto [processId, processName] : processes)
|
||||||
if (processName.value_or(L"").find(L"\\" + arg.substr(2)) != std::string::npos) Host::InjectProcess(processId);
|
if (processName.value_or(L"").find(L"\\" + arg.substr(2)) != std::string::npos) Host::InjectProcess(processId);
|
||||||
|
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
include(QtUtils)
|
include(QtUtils)
|
||||||
msvc_registry_search()
|
msvc_registry_search()
|
||||||
find_qt5(Core Widgets WebSockets)
|
find_qt5(Core Widgets WebSockets)
|
||||||
set(CMAKE_AUTOMOC ON)
|
|
||||||
cmake_policy(SET CMP0037 OLD)
|
cmake_policy(SET CMP0037 OLD)
|
||||||
|
|
||||||
add_library(Bing\ Translate MODULE bingtranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
|
add_library(Bing\ Translate MODULE bingtranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
|
||||||
add_library(Copy\ to\ Clipboard MODULE copyclipboard.cpp extensionimpl.cpp)
|
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(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(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp)
|
add_library(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp)
|
||||||
add_library(Extra\ Window MODULE extrawindow.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)
|
add_library(Google\ Translate MODULE googletranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
|
||||||
add_library(DevTools\ DeepL\ Translate MODULE devtoolsdeepltranslate.cpp devtoolswrapper.cpp devtools.cpp network.cpp extensionimpl.cpp)
|
|
||||||
add_library(Lua MODULE lua.cpp extensionimpl.cpp)
|
add_library(Lua MODULE lua.cpp extensionimpl.cpp)
|
||||||
add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp)
|
add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp)
|
||||||
add_library(Remove\ Repeated\ Characters MODULE removerepeatchar.cpp extensionimpl.cpp)
|
add_library(Remove\ Repeated\ Characters MODULE removerepeatchar.cpp extensionimpl.cpp)
|
||||||
@ -23,10 +22,10 @@ add_library(Thread\ Linker MODULE threadlinker.cpp extensionimpl.cpp)
|
|||||||
target_precompile_headers(Bing\ Translate REUSE_FROM pch)
|
target_precompile_headers(Bing\ Translate REUSE_FROM pch)
|
||||||
target_precompile_headers(Copy\ to\ Clipboard REUSE_FROM pch)
|
target_precompile_headers(Copy\ to\ Clipboard REUSE_FROM pch)
|
||||||
target_precompile_headers(DeepL\ Translate REUSE_FROM pch)
|
target_precompile_headers(DeepL\ Translate REUSE_FROM pch)
|
||||||
|
target_precompile_headers(DevTools\ DeepL\ Translate REUSE_FROM pch)
|
||||||
target_precompile_headers(Extra\ Newlines REUSE_FROM pch)
|
target_precompile_headers(Extra\ Newlines REUSE_FROM pch)
|
||||||
target_precompile_headers(Extra\ Window REUSE_FROM pch)
|
target_precompile_headers(Extra\ Window REUSE_FROM pch)
|
||||||
target_precompile_headers(Google\ Translate REUSE_FROM pch)
|
target_precompile_headers(Google\ Translate REUSE_FROM pch)
|
||||||
target_precompile_headers(DevTools\ DeepL\ Translate REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Lua REUSE_FROM pch)
|
target_precompile_headers(Lua REUSE_FROM pch)
|
||||||
target_precompile_headers(Regex\ Filter REUSE_FROM pch)
|
target_precompile_headers(Regex\ Filter REUSE_FROM pch)
|
||||||
target_precompile_headers(Remove\ Repeated\ Characters REUSE_FROM pch)
|
target_precompile_headers(Remove\ Repeated\ Characters REUSE_FROM pch)
|
||||||
@ -38,18 +37,15 @@ target_precompile_headers(Thread\ Linker REUSE_FROM pch)
|
|||||||
|
|
||||||
target_link_libraries(Bing\ Translate winhttp Qt5::Widgets)
|
target_link_libraries(Bing\ Translate winhttp Qt5::Widgets)
|
||||||
target_link_libraries(DeepL\ 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(Extra\ Window Qt5::Widgets)
|
target_link_libraries(Extra\ Window Qt5::Widgets)
|
||||||
target_link_libraries(Google\ Translate winhttp Qt5::Widgets)
|
target_link_libraries(Google\ Translate winhttp Qt5::Widgets)
|
||||||
target_link_libraries(DevTools\ DeepL\ Translate winhttp Qt5::Widgets Qt5::WebSockets)
|
|
||||||
target_link_libraries(Lua lua53 Qt5::Widgets)
|
target_link_libraries(Lua lua53 Qt5::Widgets)
|
||||||
target_link_libraries(Regex\ Filter Qt5::Widgets)
|
target_link_libraries(Regex\ Filter Qt5::Widgets)
|
||||||
target_link_libraries(Thread\ Linker Qt5::Widgets)
|
target_link_libraries(Thread\ Linker Qt5::Widgets)
|
||||||
|
|
||||||
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5WebSockets.dll)
|
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5WebSockets.dll)
|
||||||
add_custom_command(TARGET DevTools\ DeepL\ Translate
|
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5WebSocketsd.dll)
|
||||||
POST_BUILD
|
install_qt5_libs(DevTools\ DeepL\ Translate)
|
||||||
COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt"
|
endif()
|
||||||
COMMAND set PATH=%PATH%$<SEMICOLON>${qt5_install_prefix}/bin
|
endif()
|
||||||
COMMAND Qt5::windeployqt --dir $<TARGET_FILE_DIR:${PROJECT_NAME}> "$<TARGET_FILE_DIR:${PROJECT_NAME}>/DevTools\ DeepL\ Translate.dll"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#include "extension.h"
|
#include "network.h"
|
||||||
#include "network.h"
|
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
extern const wchar_t* TRANSLATION_ERROR;
|
extern const wchar_t* TRANSLATION_ERROR;
|
||||||
@ -81,7 +80,7 @@ QStringList languages
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true;
|
bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true;
|
||||||
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 500;
|
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
|
||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
{
|
{
|
||||||
@ -94,10 +93,8 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
|||||||
FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))),
|
FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))),
|
||||||
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", apiKey.Copy()).c_str()
|
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", apiKey.Copy()).c_str()
|
||||||
})
|
})
|
||||||
{
|
|
||||||
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
|
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
|
||||||
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
||||||
}
|
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
|
|
||||||
if (HttpRequest httpRequest{
|
if (HttpRequest httpRequest{
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
#include "extension.h"
|
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
@ -26,7 +25,7 @@ QStringList languages
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool translateSelectedOnly = true, rateLimitAll = true, rateLimitSelected = true, useCache = true;
|
bool translateSelectedOnly = true, rateLimitAll = true, rateLimitSelected = true, useCache = true;
|
||||||
int tokenCount = 10, tokenRestoreDelay = 60000, maxSentenceSize = 500;
|
int tokenCount = 10, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
|
||||||
|
|
||||||
enum KeyType { CAT, REST };
|
enum KeyType { CAT, REST };
|
||||||
int keyType = CAT;
|
int keyType = CAT;
|
||||||
@ -90,6 +89,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
|||||||
L"/jsonrpc",
|
L"/jsonrpc",
|
||||||
body,
|
body,
|
||||||
L"Host: www2.deepl.com\r\nAccept-Language: en-US,en;q=0.5\r\nContent-type: application/json; charset=utf-8\r\nOrigin: https://www.deepl.com\r\nTE: Trailers",
|
L"Host: www2.deepl.com\r\nAccept-Language: en-US,en;q=0.5\r\nContent-type: application/json; charset=utf-8\r\nOrigin: https://www.deepl.com\r\nTE: Trailers",
|
||||||
|
INTERNET_DEFAULT_PORT,
|
||||||
L"https://www.deepl.com/translator",
|
L"https://www.deepl.com/translator",
|
||||||
WINHTTP_FLAG_SECURE
|
WINHTTP_FLAG_SECURE
|
||||||
})
|
})
|
||||||
|
@ -1,310 +1,154 @@
|
|||||||
#include "devtools.h"
|
#include "devtools.h"
|
||||||
|
#include <QWebSocket>
|
||||||
|
#include <QMetaEnum>
|
||||||
|
#include <ppltasks.h>
|
||||||
|
|
||||||
DevTools::DevTools(QObject* parent) :
|
namespace
|
||||||
QObject(parent),
|
|
||||||
idCounter(0),
|
|
||||||
idMethod(0),
|
|
||||||
status("Stopped"),
|
|
||||||
session(0)
|
|
||||||
{
|
{
|
||||||
connect(&webSocket, &QWebSocket::stateChanged, this, &DevTools::stateChanged);
|
std::function<void(QString)> OnStatusChanged = Swallow;
|
||||||
connect(&webSocket, &QWebSocket::textMessageReceived, this, &DevTools::onTextMessageReceived);
|
PROCESS_INFORMATION processInfo = {};
|
||||||
|
std::atomic<int> idCounter = 0, idMethod = 0;
|
||||||
|
std::mutex devToolsMutex;
|
||||||
|
QWebSocket webSocket;
|
||||||
|
std::unordered_map<int, concurrency::task_completion_event<JSON::Value<wchar_t>>> mapQueue;
|
||||||
|
std::unordered_map<std::wstring, std::vector<JSON::Value<wchar_t>>> mapMethod;
|
||||||
|
auto _ = ([]
|
||||||
|
{
|
||||||
|
QObject::connect(&webSocket, &QWebSocket::stateChanged,
|
||||||
|
[](QAbstractSocket::SocketState state) { OnStatusChanged(QMetaEnum::fromType<QAbstractSocket::SocketState>().valueToKey(state)); }
|
||||||
|
);
|
||||||
|
QObject::connect(&webSocket, &QWebSocket::textMessageReceived, [](QString message)
|
||||||
|
{
|
||||||
|
auto result = JSON::Parse(S(message));
|
||||||
|
std::scoped_lock lock(devToolsMutex);
|
||||||
|
if (auto method = result[L"method"].String())
|
||||||
|
for (auto& [listenTo, results] : mapMethod) if (*method == listenTo) results.push_back(result[L"params"]);
|
||||||
|
if (auto id = result[L"id"].Number())
|
||||||
|
if (auto request = mapQueue.find((int)*id); request != mapQueue.end())
|
||||||
|
request->second.set(result), mapQueue.erase(request);
|
||||||
|
});
|
||||||
|
}(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DevTools::startDevTools(QString path, bool headless, int port)
|
namespace DevTools
|
||||||
{
|
{
|
||||||
if (startChrome(path, headless, port))
|
void Start(const std::wstring& path, std::function<void(QString)> statusChanged, bool headless, int port)
|
||||||
{
|
{
|
||||||
QJsonDocument doc;
|
OnStatusChanged = statusChanged;
|
||||||
QString webSocketDebuggerUrl;
|
DWORD exitCode = 0;
|
||||||
if (GetJsonfromHTTP(doc, "/json/list", port))
|
auto args = FormatString(
|
||||||
|
L"%s --proxy-server=direct:// --disable-extensions --disable-gpu --user-data-dir=%s\\devtoolscache --remote-debugging-port=%d",
|
||||||
|
path,
|
||||||
|
std::filesystem::current_path().wstring(),
|
||||||
|
port
|
||||||
|
);
|
||||||
|
if (headless) args += L"--headless";
|
||||||
|
STARTUPINFOW DUMMY = { sizeof(DUMMY) };
|
||||||
|
if ((GetExitCodeProcess(processInfo.hProcess, &exitCode) && exitCode == STILL_ACTIVE) ||
|
||||||
|
CreateProcessW(NULL, args.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &DUMMY, &processInfo)
|
||||||
|
)
|
||||||
{
|
{
|
||||||
for (const auto obj : doc.array())
|
if (HttpRequest httpRequest{
|
||||||
if (obj.toObject().value("type") == "page")
|
L"Mozilla/5.0 Textractor",
|
||||||
|
L"127.0.0.1",
|
||||||
|
L"POST",
|
||||||
|
L"/json/list",
|
||||||
|
"",
|
||||||
|
NULL,
|
||||||
|
(DWORD)port,
|
||||||
|
NULL,
|
||||||
|
WINHTTP_FLAG_ESCAPE_DISABLE
|
||||||
|
})
|
||||||
|
{
|
||||||
|
if (auto list = Copy(JSON::Parse(httpRequest.response).Array())) if (auto it = std::find_if(
|
||||||
|
list->begin(),
|
||||||
|
list->end(),
|
||||||
|
[](const JSON::Value<wchar_t>& object) { return object[L"type"].String() && *object[L"type"].String() == L"page" && object[L"webSocketDebuggerUrl"].String(); }
|
||||||
|
); it != list->end())
|
||||||
{
|
{
|
||||||
webSocketDebuggerUrl.append(obj.toObject().value("webSocketDebuggerUrl").toString());
|
std::scoped_lock lock(devToolsMutex);
|
||||||
break;
|
webSocket.open(S(*(*it)[L"webSocketDebuggerUrl"].String()));
|
||||||
|
if (HttpRequest httpRequest{
|
||||||
|
L"Mozilla/5.0 Textractor",
|
||||||
|
L"127.0.0.1",
|
||||||
|
L"POST",
|
||||||
|
L"/json/version",
|
||||||
|
"",
|
||||||
|
NULL,
|
||||||
|
(DWORD)port,
|
||||||
|
NULL,
|
||||||
|
WINHTTP_FLAG_ESCAPE_DISABLE
|
||||||
|
})
|
||||||
|
if (auto userAgent = Copy(JSON::Parse(httpRequest.response)[L"User-Agent"].String()))
|
||||||
|
if (userAgent->find(L"Headless") != std::string::npos)
|
||||||
|
SendRequest(L"Network.setUserAgentOverride", FormatString(LR"({"userAgent":"%s"})", userAgent->replace(userAgent->find(L"Headless"), 8, L"")));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
if (!webSocketDebuggerUrl.isEmpty())
|
|
||||||
{
|
|
||||||
if (GetJsonfromHTTP(doc, "/json/version", port))
|
|
||||||
{
|
|
||||||
userAgent = doc.object().value("User-Agent").toString();
|
|
||||||
}
|
|
||||||
webSocket.open(webSocketDebuggerUrl);
|
|
||||||
session += 1;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
OnStatusChanged("Failed Connection");
|
||||||
}
|
}
|
||||||
status = "Failed to find Chrome debug port!";
|
|
||||||
emit statusChanged(status);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
status = "Failed to start Chrome!";
|
|
||||||
emit statusChanged(status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int DevTools::getSession()
|
|
||||||
{
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DevTools::getStatus()
|
|
||||||
{
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString DevTools::getUserAgent()
|
|
||||||
{
|
|
||||||
return userAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
DevTools::~DevTools()
|
|
||||||
{
|
|
||||||
closeDevTools();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DevTools::startChrome(QString path, bool headless, int port)
|
|
||||||
{
|
|
||||||
if (!std::filesystem::exists(path.toStdWString()))
|
|
||||||
return false;
|
|
||||||
DWORD exitCode = 0;
|
|
||||||
if (GetExitCodeProcess(processInfo.hProcess, &exitCode) != FALSE && exitCode == STILL_ACTIVE)
|
|
||||||
return false;
|
|
||||||
QString args = "--proxy-server=direct:// --disable-extensions --disable-gpu --user-data-dir="
|
|
||||||
+ QString::fromStdWString(std::filesystem::current_path())
|
|
||||||
+ "\\devtoolscache --remote-debugging-port="
|
|
||||||
+ QString::number(port);
|
|
||||||
if (headless)
|
|
||||||
args += " --headless";
|
|
||||||
STARTUPINFOW dummy = { sizeof(dummy) };
|
|
||||||
if (!CreateProcessW(NULL, (wchar_t*)(path + " " + args).utf16(), nullptr, nullptr,
|
|
||||||
FALSE, 0, nullptr, nullptr, &dummy, &processInfo))
|
|
||||||
return false;
|
|
||||||
else
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DevTools::GetJsonfromHTTP(QJsonDocument& doc, QString object, int port)
|
|
||||||
{
|
|
||||||
if (HttpRequest httpRequest{
|
|
||||||
L"Mozilla/5.0 Textractor",
|
|
||||||
L"127.0.0.1",
|
|
||||||
L"POST",
|
|
||||||
object.toStdWString().c_str(),
|
|
||||||
"",
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
WINHTTP_FLAG_ESCAPE_DISABLE,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
DWORD(port)
|
|
||||||
})
|
|
||||||
{
|
|
||||||
QString qtString = QString::fromStdWString(httpRequest.response);
|
|
||||||
doc = QJsonDocument::fromJson(qtString.toUtf8());
|
|
||||||
if (!doc.isEmpty())
|
|
||||||
return true;
|
|
||||||
else
|
else
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void DevTools::stateChanged(QAbstractSocket::SocketState state)
|
|
||||||
{
|
|
||||||
QMetaEnum metaenum = QMetaEnum::fromType<QAbstractSocket::SocketState>();
|
|
||||||
status = metaenum.valueToKey(state);
|
|
||||||
emit statusChanged(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DevTools::SendRequest(QString method, QJsonObject params, QJsonObject& root)
|
|
||||||
{
|
|
||||||
if (!isConnected())
|
|
||||||
return false;
|
|
||||||
root = QJsonObject();
|
|
||||||
QJsonObject json;
|
|
||||||
task_completion_event<QJsonObject> response;
|
|
||||||
long id = idIncrement();
|
|
||||||
json.insert("id", id);
|
|
||||||
json.insert("method", method);
|
|
||||||
json.insert("params", params);
|
|
||||||
QJsonDocument doc(json);
|
|
||||||
QString message(doc.toJson(QJsonDocument::Compact));
|
|
||||||
mutex.lock();
|
|
||||||
mapQueue.insert(std::make_pair(id, response));
|
|
||||||
mutex.unlock();
|
|
||||||
webSocket.sendTextMessage(message);
|
|
||||||
webSocket.flush();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
root = create_task(response).get();
|
|
||||||
}
|
|
||||||
catch (const std::exception& ex)
|
|
||||||
{
|
|
||||||
response.set_exception(ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!root.isEmpty())
|
|
||||||
{
|
|
||||||
if (root.contains("error"))
|
|
||||||
{
|
{
|
||||||
return false;
|
OnStatusChanged("Failed Startup");
|
||||||
}
|
|
||||||
else if (root.contains("result"))
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long DevTools::methodToReceive(QString method, QJsonObject params)
|
|
||||||
{
|
|
||||||
QJsonObject json;
|
|
||||||
long id = idmIncrement();
|
|
||||||
json.insert("method", method);
|
|
||||||
json.insert("params", params);
|
|
||||||
mutex.lock();
|
|
||||||
mapMethod.insert(std::make_pair(id, json));
|
|
||||||
mutex.unlock();
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
long DevTools::idIncrement()
|
|
||||||
{
|
|
||||||
return ++idCounter;
|
|
||||||
}
|
|
||||||
|
|
||||||
long DevTools::idmIncrement()
|
|
||||||
{
|
|
||||||
return ++idMethod;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DevTools::isConnected()
|
|
||||||
{
|
|
||||||
if (webSocket.state() == QAbstractSocket::ConnectedState)
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DevTools::compareJson(QJsonValue stored, QJsonValue params)
|
|
||||||
{
|
|
||||||
if (stored.isObject())
|
|
||||||
{
|
|
||||||
foreach(const QString & key, stored.toObject().keys())
|
|
||||||
{
|
|
||||||
QJsonValue storedvalue = stored.toObject().value(key);
|
|
||||||
QJsonValue value = params.toObject().value(key);
|
|
||||||
if (!compareJson(storedvalue, value))
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (stored.isArray())
|
|
||||||
{
|
|
||||||
for (int i = 0; i < stored.toArray().size(); i++)
|
|
||||||
if (!compareJson(stored.toArray()[i], params.toArray()[i]))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (stored.isString())
|
|
||||||
{
|
|
||||||
if (!stored.toString().contains(params.toString()))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (stored.toVariant() != params.toVariant())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
void Close()
|
||||||
}
|
|
||||||
|
|
||||||
bool DevTools::checkMethod(long id)
|
|
||||||
{
|
|
||||||
MapMethod::iterator iter = mapMethod.find(id);
|
|
||||||
if (iter == mapMethod.end())
|
|
||||||
return true;
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DevTools::onTextMessageReceived(QString message)
|
|
||||||
{
|
|
||||||
QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8());
|
|
||||||
if (doc.isObject())
|
|
||||||
{
|
{
|
||||||
QJsonObject root = doc.object();
|
std::scoped_lock lock(devToolsMutex);
|
||||||
if (root.contains("method"))
|
for (const auto& [_, task] : mapQueue) task.set_exception(std::runtime_error("closed"));
|
||||||
{
|
webSocket.close();
|
||||||
for (auto iter = mapMethod.cbegin(); iter != mapMethod.cend();)
|
mapMethod.clear();
|
||||||
{
|
mapQueue.clear();
|
||||||
if (iter->second.value("method") == root.value("method")
|
DWORD exitCode = 0;
|
||||||
&& compareJson(iter->second.value("params"), root.value("params")))
|
if (GetExitCodeProcess(processInfo.hProcess, &exitCode) && exitCode == STILL_ACTIVE)
|
||||||
{
|
|
||||||
mutex.lock();
|
|
||||||
mapMethod.erase(iter++);
|
|
||||||
mutex.unlock();
|
|
||||||
}
|
|
||||||
++iter;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (root.contains("id"))
|
|
||||||
{
|
|
||||||
long id = root.value("id").toInt();
|
|
||||||
MapResponse::iterator request = mapQueue.find(id);
|
|
||||||
if (request != mapQueue.end())
|
|
||||||
{
|
|
||||||
request->second.set(root);
|
|
||||||
mutex.lock();
|
|
||||||
mapQueue.erase(request);
|
|
||||||
mutex.unlock();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DevTools::closeDevTools()
|
|
||||||
{
|
|
||||||
if (this->mapQueue.size() > 0)
|
|
||||||
{
|
|
||||||
MapResponse::iterator iter = mapQueue.begin();
|
|
||||||
MapResponse::iterator iend = mapQueue.end();
|
|
||||||
for (; iter != iend; iter++)
|
|
||||||
{
|
|
||||||
iter->second.set_exception("exception");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
webSocket.close();
|
|
||||||
mapMethod.clear();
|
|
||||||
mapQueue.clear();
|
|
||||||
idCounter = 0;
|
|
||||||
idMethod = 0;
|
|
||||||
DWORD exitCode = 0;
|
|
||||||
if (GetExitCodeProcess(processInfo.hProcess, &exitCode) != FALSE)
|
|
||||||
{
|
|
||||||
if (exitCode == STILL_ACTIVE)
|
|
||||||
{
|
{
|
||||||
TerminateProcess(processInfo.hProcess, 0);
|
TerminateProcess(processInfo.hProcess, 0);
|
||||||
WaitForSingleObject(processInfo.hProcess, 100);
|
WaitForSingleObject(processInfo.hProcess, 100);
|
||||||
CloseHandle(processInfo.hProcess);
|
CloseHandle(processInfo.hProcess);
|
||||||
CloseHandle(processInfo.hThread);
|
CloseHandle(processInfo.hThread);
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(200));
|
try { std::filesystem::remove_all(L"devtoolscache"); } catch (std::filesystem::filesystem_error) {}
|
||||||
try
|
OnStatusChanged("Stopped");
|
||||||
{
|
|
||||||
std::filesystem::remove_all(L"devtoolscache");
|
|
||||||
}
|
|
||||||
catch (const std::exception&)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
status = "Stopped";
|
|
||||||
emit statusChanged(status);
|
bool Connected()
|
||||||
}
|
{
|
||||||
|
std::scoped_lock lock(devToolsMutex);
|
||||||
|
return webSocket.state() == QAbstractSocket::ConnectedState;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSON::Value<wchar_t> SendRequest(const std::wstring& method, const std::wstring& params)
|
||||||
|
{
|
||||||
|
if (webSocket.state() != QAbstractSocket::ConnectedState) return {};
|
||||||
|
concurrency::task_completion_event<JSON::Value<wchar_t>> response;
|
||||||
|
int id = idCounter +=1;
|
||||||
|
auto message = FormatString(LR"({"id":%d,"method":"%s","params":%s})", id, method, params);
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(devToolsMutex);
|
||||||
|
mapQueue.try_emplace(id, response);
|
||||||
|
webSocket.sendTextMessage(S(message));
|
||||||
|
webSocket.flush();
|
||||||
|
}
|
||||||
|
try { if (auto result = create_task(response).get()[L"result"]) return result; } catch (...) {}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartListening(const std::wstring& method)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(devToolsMutex);
|
||||||
|
mapMethod.try_emplace(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<JSON::Value<wchar_t>> ListenResults(const std::wstring& method)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(devToolsMutex);
|
||||||
|
return mapMethod[method];
|
||||||
|
}
|
||||||
|
|
||||||
|
void StopListening(const std::wstring& method)
|
||||||
|
{
|
||||||
|
std::scoped_lock lock(devToolsMutex);
|
||||||
|
mapMethod.erase(method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,51 +1,13 @@
|
|||||||
#include <QtCore>
|
#include "qtcommon.h"
|
||||||
#include <QtWebSockets/QWebSocket>
|
|
||||||
#include <ppltasks.h>
|
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
|
|
||||||
using namespace Concurrency;
|
namespace DevTools
|
||||||
|
{
|
||||||
typedef std::map<long, task_completion_event<QJsonObject>> MapResponse;
|
void Start(const std::wstring& path, std::function<void(QString)> statusChanged, bool headless, int port);
|
||||||
typedef std::map<long, QJsonObject> MapMethod;
|
void Close();
|
||||||
|
bool Connected();
|
||||||
class DevTools : public QObject {
|
JSON::Value<wchar_t> SendRequest(const std::wstring& method, const std::wstring& params = L"{}");
|
||||||
Q_OBJECT
|
void StartListening(const std::wstring& method);
|
||||||
public:
|
std::vector<JSON::Value<wchar_t>> ListenResults(const std::wstring& method);
|
||||||
explicit DevTools(QObject* parent = nullptr);
|
void StopListening(const std::wstring& method);
|
||||||
~DevTools();
|
}
|
||||||
|
|
||||||
Q_SIGNALS:
|
|
||||||
void statusChanged(const QString&);
|
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
void stateChanged(QAbstractSocket::SocketState state);
|
|
||||||
void onTextMessageReceived(QString message);
|
|
||||||
|
|
||||||
public:
|
|
||||||
void startDevTools(QString path, bool headless = false, int port = 9222);
|
|
||||||
void closeDevTools();
|
|
||||||
bool checkMethod(long id);
|
|
||||||
int getSession();
|
|
||||||
bool SendRequest(QString method, QJsonObject params, QJsonObject& root);
|
|
||||||
long methodToReceive(QString method, QJsonObject params = {});
|
|
||||||
QString getStatus();
|
|
||||||
QString getUserAgent();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool isConnected();
|
|
||||||
bool startChrome(QString path, bool headless = false, int port = 9222);
|
|
||||||
bool GetJsonfromHTTP(QJsonDocument& doc, QString object, int port = 9222);
|
|
||||||
long idIncrement();
|
|
||||||
long idmIncrement();
|
|
||||||
bool compareJson(QJsonValue stored, QJsonValue params);
|
|
||||||
int session;
|
|
||||||
QWebSocket webSocket;
|
|
||||||
std::mutex mutex;
|
|
||||||
MapResponse mapQueue;
|
|
||||||
MapMethod mapMethod;
|
|
||||||
long idCounter;
|
|
||||||
long idMethod;
|
|
||||||
PROCESS_INFORMATION processInfo;
|
|
||||||
QString status;
|
|
||||||
QString userAgent;
|
|
||||||
};
|
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
#include "extension.h"
|
|
||||||
#include "devtools.h"
|
#include "devtools.h"
|
||||||
|
#include <ShlObj.h>
|
||||||
|
|
||||||
extern const wchar_t* TRANSLATION_ERROR;
|
extern const wchar_t* TRANSLATION_ERROR;
|
||||||
extern Synchronized<std::wstring> translateTo, translateFrom;
|
extern Synchronized<std::wstring> translateTo;
|
||||||
|
extern QFormLayout* display;
|
||||||
bool useCache = true, autoStartChrome = false, headlessChrome = true;
|
extern Settings settings;
|
||||||
int maxSentenceSize = 500, chromePort = 9222;
|
|
||||||
|
|
||||||
const char* TRANSLATION_PROVIDER = "DevTools DeepL Translate";
|
const char* TRANSLATION_PROVIDER = "DevTools DeepL Translate";
|
||||||
const wchar_t* ERROR_CHROME = L"Error: Chrome not started";
|
const char* GET_API_KEY_FROM = nullptr;
|
||||||
const wchar_t* ERROR_START_CHROME = L"Error: failed to start Chrome or to connect to it";
|
bool translateSelectedOnly = true, rateLimitAll = false, rateLimitSelected = false, useCache = true;
|
||||||
const wchar_t* ERROR_GOT_TIMEOUT = L"Error: timeout (s)";
|
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 10000;
|
||||||
const wchar_t* ERROR_COMMAND_FAIL = L"Error: command failed";
|
|
||||||
const wchar_t* ERROR_LANGUAGE = L"Error: target languages do not match";
|
|
||||||
const wchar_t* ERROR_NOTE = L"Error: notification";
|
|
||||||
const wchar_t* ERROR_EMPTY_ANSWER = L"Error: empty translation";
|
|
||||||
|
|
||||||
QString URL = "https://www.deepl.com/en/translator";
|
extern const char* CHROME_LOCATION;
|
||||||
QStringList languagesTo
|
extern const char* START_DEVTOOLS;
|
||||||
|
extern const char* STOP_DEVTOOLS;
|
||||||
|
extern const char* HEADLESS_MODE;
|
||||||
|
extern const char* DEVTOOLS_STATUS;
|
||||||
|
extern const char* AUTO_START;
|
||||||
|
extern const wchar_t* ERROR_START_CHROME;
|
||||||
|
|
||||||
|
QStringList languages
|
||||||
{
|
{
|
||||||
"Chinese (simplified): zh",
|
"Chinese (simplified): zh",
|
||||||
"Dutch: nl",
|
"Dutch: nl",
|
||||||
@ -33,323 +35,97 @@ QStringList languagesTo
|
|||||||
"Spanish: es",
|
"Spanish: es",
|
||||||
};
|
};
|
||||||
|
|
||||||
QStringList languagesFrom
|
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||||
{
|
{
|
||||||
"Any language: ",
|
switch (ul_reason_for_call)
|
||||||
"Chinese: zh",
|
|
||||||
"Dutch: nl",
|
|
||||||
"English: en",
|
|
||||||
"French: fr",
|
|
||||||
"German: de",
|
|
||||||
"Italian: it",
|
|
||||||
"Japanese: ja",
|
|
||||||
"Polish: pl",
|
|
||||||
"Portuguese: pt",
|
|
||||||
"Russian: ru",
|
|
||||||
"Spanish: es",
|
|
||||||
};
|
|
||||||
|
|
||||||
int docFound = -1, targetNodeId = -1, session = -1, pageEnabled = -1, userAgentFlag = -1, backup = -1, sourceLangId = -1, mobileShareId = -1;
|
|
||||||
long update = -1, callNumber = 0;
|
|
||||||
std::vector<long> callQueue;
|
|
||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, DevTools* devTools)
|
|
||||||
{
|
|
||||||
QString qtext = S(text);
|
|
||||||
qtext.remove(QString(12288)); // japanese space (no need for translator)
|
|
||||||
qtext.replace(QString(12289), ","); // replace the japanese comma with the latin comma for correct translation
|
|
||||||
|
|
||||||
// Remove quotes
|
|
||||||
bool checkQuote = false;
|
|
||||||
if ((qtext.front() == QString(12300) && qtext.back() == QString(12301)) // japanese quotation marks
|
|
||||||
|| (qtext.front() == "\"" && qtext.back() == "\""))
|
|
||||||
{
|
{
|
||||||
checkQuote = true;
|
case DLL_PROCESS_ATTACH:
|
||||||
qtext.remove(0, 1);
|
|
||||||
qtext.chop(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check specific cases (sentence has only one japanese symbol or consists of ellipsis)
|
|
||||||
int count = qtext.count(QString(8230)); // ellipsis
|
|
||||||
if (count == qtext.length()
|
|
||||||
|| (count == (qtext.length() - 1) && qtext.back() == QString(12290))) // japanese end of a sentence
|
|
||||||
{
|
{
|
||||||
return { true, text };
|
QString chromePath = settings.value(CHROME_LOCATION).toString();
|
||||||
}
|
wchar_t programFiles[MAX_PATH + 100] = {};
|
||||||
if (count == (qtext.length() - 1))
|
if (chromePath.isEmpty()) for (auto folder : { CSIDL_PROGRAM_FILESX86, CSIDL_PROGRAM_FILES, CSIDL_LOCAL_APPDATA })
|
||||||
{
|
|
||||||
qtext.remove(QString(8230));
|
|
||||||
qtext += QString(12290) + QString(8230); // add the end symbol for correct translation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put quotes back
|
|
||||||
if (checkQuote)
|
|
||||||
{
|
|
||||||
qtext = "\"" + qtext + "\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check status
|
|
||||||
if (devTools->getStatus() == "Stopped")
|
|
||||||
{
|
|
||||||
return { false, FormatString(L"%s", ERROR_CHROME) };
|
|
||||||
}
|
|
||||||
if (devTools->getStatus().startsWith("Fail") || devTools->getStatus().startsWith("Unconnected"))
|
|
||||||
{
|
|
||||||
return { false, FormatString(L"%s", ERROR_START_CHROME) };
|
|
||||||
}
|
|
||||||
if (session != devTools->getSession())
|
|
||||||
{
|
|
||||||
session = devTools->getSession();
|
|
||||||
docFound = -1;
|
|
||||||
targetNodeId = -1;
|
|
||||||
sourceLangId = -1;
|
|
||||||
mobileShareId = -1;
|
|
||||||
pageEnabled = -1;
|
|
||||||
userAgentFlag = -1;
|
|
||||||
update = -1;
|
|
||||||
callNumber = 0;
|
|
||||||
backup = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove tags and reduce the number of ellipsis for correct translation
|
|
||||||
qtext.remove(QRegExp("<[^>]*>"));
|
|
||||||
qtext.replace(QRegExp("(" + QString(8230) + ")+"), " " + QString(8230));
|
|
||||||
|
|
||||||
// Enable page feedback
|
|
||||||
QJsonObject root;
|
|
||||||
int errorCode = 0;
|
|
||||||
if (pageEnabled == -1)
|
|
||||||
{
|
|
||||||
if (!devTools->SendRequest("Page.enable", {}, root))
|
|
||||||
errorCode = 1;
|
|
||||||
else
|
|
||||||
pageEnabled = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change user-agent if in headless mode
|
|
||||||
if (userAgentFlag == -1 && errorCode == 0)
|
|
||||||
{
|
|
||||||
QString userAgent = devTools->getUserAgent();
|
|
||||||
if (!userAgent.isEmpty())
|
|
||||||
{
|
{
|
||||||
userAgent.replace("HeadlessChrome", "Chrome");
|
SHGetFolderPathW(NULL, folder, NULL, SHGFP_TYPE_CURRENT, programFiles);
|
||||||
if (!devTools->SendRequest("Network.setUserAgentOverride", { {"userAgent", userAgent} }, root))
|
wcscat_s(programFiles, L"/Google/Chrome/Application/chrome.exe");
|
||||||
errorCode = 1;
|
if (std::filesystem::exists(programFiles)) chromePath = S(programFiles);
|
||||||
else
|
|
||||||
userAgentFlag = 1;
|
|
||||||
}
|
}
|
||||||
|
auto chromePathEdit = new QLineEdit(chromePath);
|
||||||
|
QObject::connect(chromePathEdit, &QLineEdit::textChanged, [chromePathEdit](QString path) { settings.setValue(CHROME_LOCATION, path); });
|
||||||
|
display->addRow(CHROME_LOCATION, chromePathEdit);
|
||||||
|
auto statusLabel = new QLabel("Stopped");
|
||||||
|
auto startButton = new QPushButton(START_DEVTOOLS), stopButton = new QPushButton(STOP_DEVTOOLS);
|
||||||
|
auto headlessCheckBox = new QCheckBox(HEADLESS_MODE);
|
||||||
|
headlessCheckBox->setChecked(settings.value(HEADLESS_MODE, true).toBool());
|
||||||
|
QObject::connect(headlessCheckBox, &QCheckBox::clicked, [](bool headless) { settings.setValue(HEADLESS_MODE, headless); });
|
||||||
|
QObject::connect(startButton, &QPushButton::clicked, [statusLabel, chromePathEdit, headlessCheckBox] {
|
||||||
|
DevTools::Start(
|
||||||
|
S(chromePathEdit->text()),
|
||||||
|
[statusLabel](QString status) { QMetaObject::invokeMethod(statusLabel, std::bind(&QLabel::setText, statusLabel, status)); },
|
||||||
|
headlessCheckBox->isChecked(),
|
||||||
|
9222
|
||||||
|
);
|
||||||
|
if (!DevTools::SendRequest(L"Network.enable")) DevTools::Close();
|
||||||
|
});
|
||||||
|
QObject::connect(stopButton, &QPushButton::clicked, &DevTools::Close);
|
||||||
|
auto buttons = new QHBoxLayout();
|
||||||
|
buttons->addWidget(startButton);
|
||||||
|
buttons->addWidget(stopButton);
|
||||||
|
display->addRow(buttons);
|
||||||
|
display->addRow(headlessCheckBox);
|
||||||
|
auto autoStartButton = new QCheckBox(AUTO_START);
|
||||||
|
autoStartButton->setChecked(settings.value(AUTO_START, false).toBool());
|
||||||
|
QObject::connect(autoStartButton, &QCheckBox::clicked, [](bool autoStart) {settings.setValue(AUTO_START, autoStart); });
|
||||||
|
display->addRow(autoStartButton);
|
||||||
|
statusLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
|
||||||
|
display->addRow(DEVTOOLS_STATUS, statusLabel);
|
||||||
|
if (autoStartButton->isChecked()) startButton->click();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
// Increase queue counter and wait until previous calls are done
|
case DLL_PROCESS_DETACH:
|
||||||
float timer = 0;
|
|
||||||
int timerStop = 10;
|
|
||||||
long callTag = ++callNumber;
|
|
||||||
callQueue.insert(callQueue.begin(), callTag);
|
|
||||||
while (errorCode == 0 && callQueue.back() != callTag && timer < 2 * timerStop)
|
|
||||||
{
|
{
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
DevTools::Close();
|
||||||
timer += 0.1;
|
|
||||||
}
|
}
|
||||||
if (timer >= 2 * timerStop)
|
break;
|
||||||
errorCode = 5;
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
// Set methods to receive
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
long navigate = devTools->methodToReceive("Page.navigatedWithinDocument");
|
{
|
||||||
long target;
|
if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) };
|
||||||
if (mobileShareId != -1 && backup == -1)
|
|
||||||
target = devTools->methodToReceive("DOM.attributeModified", { { "nodeId" , mobileShareId } , { "value" , "lmt__mobile_share_container lmt--mobile-hidden" } });
|
// DevTools can't handle concurrent translations yet
|
||||||
else if (mobileShareId == -1 && backup == -1)
|
static std::mutex translationMutex;
|
||||||
target = devTools->methodToReceive("DOM.attributeModified", { { "value" , "lmt__mobile_share_container lmt--mobile-hidden" } });
|
std::scoped_lock lock(translationMutex);
|
||||||
else
|
|
||||||
target = devTools->methodToReceive("DOM.childNodeCountUpdated");
|
|
||||||
if (update == -1)
|
|
||||||
update = devTools->methodToReceive("DOM.documentUpdated");
|
|
||||||
|
|
||||||
// Navigate to site and wait until it is loaded
|
// Navigate to site and wait until it is loaded
|
||||||
QString checkFrom = translateFrom.Copy().empty() ? "ja" : S(translateFrom.Copy());
|
DevTools::StartListening(L"Network.responseReceived");
|
||||||
QString fullUrl = URL + "#" + checkFrom + "/" + S(translateTo.Copy()) + "/" + qtext;
|
DevTools::SendRequest(L"Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/translator#any/%s/%s"})", translateTo.Copy(), Escape(text)));
|
||||||
if (errorCode == 0 && !devTools->SendRequest("Page.navigate", { {"url", fullUrl} }, root))
|
for (int retry = 0; ++retry < 50; Sleep(100))
|
||||||
errorCode = 1;
|
for (const auto& result : DevTools::ListenResults(L"Network.responseReceived"))
|
||||||
timer = 0;
|
if (auto URL = result[L"response"][L"url"].String())
|
||||||
while (errorCode == 0 && !devTools->checkMethod(navigate) && timer < timerStop)
|
if (URL->find(L"deepl.com/jsonrpc") != std::string::npos) break;
|
||||||
{
|
DevTools::StopListening(L"Network.responseReceived");
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
||||||
timer += 0.1;
|
|
||||||
}
|
|
||||||
if (timer >= timerStop)
|
|
||||||
errorCode = 2;
|
|
||||||
|
|
||||||
// Check if document is outdated
|
// Extract translation from site
|
||||||
if (devTools->checkMethod(update))
|
auto RemoveTags = [](const std::wstring& HTML)
|
||||||
{
|
{
|
||||||
docFound = -1;
|
std::wstring result;
|
||||||
targetNodeId = -1;
|
for (unsigned i = 0; i < HTML.size(); ++i)
|
||||||
update = -1;
|
if (HTML[i] == '<') i = HTML.find('>', i);
|
||||||
}
|
else result.push_back(HTML[i]);
|
||||||
|
return result;
|
||||||
// Get document
|
};
|
||||||
if (docFound == -1 && errorCode == 0)
|
if (auto document = Copy(DevTools::SendRequest(L"DOM.getDocument")[L"root"][L"nodeId"].Number()))
|
||||||
{
|
if (auto target = Copy(DevTools::SendRequest(
|
||||||
if (!devTools->SendRequest("DOM.getDocument", {}, root))
|
L"DOM.querySelector", FormatString(LR"({"nodeId":%d,"selector":"#target-dummydiv"})", (int)document.value())
|
||||||
errorCode = 1;
|
)[L"nodeId"].Number()))
|
||||||
else
|
if (auto outerHTML = Copy(DevTools::SendRequest(L"DOM.getOuterHTML", FormatString(LR"({"nodeId":%d})", (int)target.value()))[L"outerHTML"].String()))
|
||||||
docFound = root.value("result").toObject().value("root").toObject().value("nodeId").toInt();
|
if (auto translation = RemoveTags(outerHTML.value()); !translation.empty()) return { true, translation };
|
||||||
}
|
else if (target = Copy(DevTools::SendRequest(
|
||||||
|
L"DOM.querySelector", FormatString(LR"({"nodeId":%d,"selector":"div.lmt__system_notification"})", (int)document.value())
|
||||||
// Get target selector
|
)[L"nodeId"].Number()))
|
||||||
if (targetNodeId == -1 && errorCode == 0)
|
if (outerHTML = Copy(DevTools::SendRequest(L"DOM.getOuterHTML", FormatString(LR"({"nodeId":%d})", (int)target.value()))[L"outerHTML"].String()))
|
||||||
{
|
return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, RemoveTags(outerHTML.value())) };
|
||||||
if (!devTools->SendRequest("DOM.querySelector", { {"nodeId", docFound}, {"selector", "textarea.lmt__target_textarea"} }, root)
|
return { false, TRANSLATION_ERROR };
|
||||||
|| root.value("result").toObject().value("nodeId").toInt() == 0)
|
|
||||||
{
|
|
||||||
docFound = -1;
|
|
||||||
errorCode = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
targetNodeId = root.value("result").toObject().value("nodeId").toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get source language selector
|
|
||||||
if (sourceLangId == -1 && errorCode == 0)
|
|
||||||
{
|
|
||||||
if (!devTools->SendRequest("DOM.querySelector", { {"nodeId", docFound}, {"selector", "div.lmt__language_select--source"} }, root)
|
|
||||||
|| root.value("result").toObject().value("nodeId").toInt() == 0)
|
|
||||||
{
|
|
||||||
docFound = -1;
|
|
||||||
errorCode = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
sourceLangId = root.value("result").toObject().value("nodeId").toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get mobile share selector
|
|
||||||
if (mobileShareId == -1 && errorCode == 0)
|
|
||||||
{
|
|
||||||
if (!devTools->SendRequest("DOM.querySelector", { {"nodeId", docFound}, {"selector", "div.lmt__mobile_share_container"} }, root)
|
|
||||||
|| root.value("result").toObject().value("nodeId").toInt() == 0)
|
|
||||||
{
|
|
||||||
docFound = -1;
|
|
||||||
errorCode = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
mobileShareId = root.value("result").toObject().value("nodeId").toInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the translation to appear on the web page
|
|
||||||
timer = 0;
|
|
||||||
while (errorCode == 0 && !devTools->checkMethod(target) && timer < timerStop)
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
|
||||||
timer += 0.1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Catch the translation
|
|
||||||
QString OuterHTML;
|
|
||||||
if (errorCode == 0)
|
|
||||||
{
|
|
||||||
if (!devTools->SendRequest("DOM.getOuterHTML", { {"nodeId", targetNodeId + 1} }, root))
|
|
||||||
{
|
|
||||||
targetNodeId = -1;
|
|
||||||
errorCode = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
OuterHTML = root.value("result").toObject().value("outerHTML").toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (errorCode == 0 && OuterHTML == "<div></div>")
|
|
||||||
{
|
|
||||||
// Try to catch the notification
|
|
||||||
int noteNodeId = -1;
|
|
||||||
if (!devTools->SendRequest("DOM.querySelector", { {"nodeId", docFound}, {"selector", "div.lmt__system_notification"} }, root)
|
|
||||||
|| root.value("result").toObject().value("nodeId").toInt() == 0)
|
|
||||||
{
|
|
||||||
if (timer >= timerStop)
|
|
||||||
errorCode = 2;
|
|
||||||
else
|
|
||||||
errorCode = 6;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
noteNodeId = root.value("result").toObject().value("nodeId").toInt();
|
|
||||||
if (errorCode == 0 && devTools->SendRequest("DOM.getOuterHTML", { {"nodeId", noteNodeId} }, root))
|
|
||||||
{
|
|
||||||
OuterHTML = root.value("result").toObject().value("outerHTML").toString();
|
|
||||||
}
|
|
||||||
errorCode = 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
OuterHTML.remove(QRegExp("<[^>]*>"));
|
|
||||||
OuterHTML = OuterHTML.trimmed();
|
|
||||||
if (backup == -1 && errorCode == 0 && timer >= timerStop)
|
|
||||||
backup = 1;
|
|
||||||
|
|
||||||
// Check if the translator output language does not match the selected language
|
|
||||||
QString targetLang;
|
|
||||||
if (errorCode == 0 && devTools->SendRequest("DOM.getAttributes", { {"nodeId", targetNodeId} }, root))
|
|
||||||
{
|
|
||||||
QJsonArray attributes = root.value("result").toObject().value("attributes").toArray();
|
|
||||||
for (size_t i = 0; i < attributes.size(); i++)
|
|
||||||
{
|
|
||||||
if (attributes[i].toString() == "lang")
|
|
||||||
{
|
|
||||||
targetLang = attributes[i + 1].toString().mid(0, 2);
|
|
||||||
if (targetLang != S(translateTo.Copy()))
|
|
||||||
{
|
|
||||||
errorCode = 4;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check selected source language
|
|
||||||
if (errorCode == 0 && devTools->SendRequest("DOM.getAttributes", { {"nodeId", sourceLangId} }, root))
|
|
||||||
{
|
|
||||||
QJsonArray attributes = root.value("result").toObject().value("attributes").toArray();
|
|
||||||
for (size_t i = 0; i < attributes.size(); i++)
|
|
||||||
{
|
|
||||||
if (attributes[i].toString() == "dl-selected-lang"
|
|
||||||
&& attributes[i + 1].toString().mid(0, 2) != S(translateFrom.Copy()))
|
|
||||||
{
|
|
||||||
QStringList::const_iterator constIter;
|
|
||||||
for (constIter = languagesFrom.constBegin(); constIter != languagesFrom.constEnd(); ++constIter)
|
|
||||||
{
|
|
||||||
if (constIter->contains(": " + S(translateFrom.Copy())))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
devTools->SendRequest("Runtime.evaluate", { {"expression",
|
|
||||||
"document\
|
|
||||||
.querySelector('div.lmt__language_select--source')\
|
|
||||||
.querySelector('button.lmt__language_select__active')\
|
|
||||||
.click();\
|
|
||||||
document\
|
|
||||||
.evaluate(\"//button[contains(text(), '"
|
|
||||||
+ constIter->split(": ")[0] + "')]\", \
|
|
||||||
document.querySelector('div.lmt__language_select__menu'),\
|
|
||||||
null, XPathResult.FIRST_ORDERED_NODE_TYPE, null)\
|
|
||||||
.singleNodeValue\
|
|
||||||
.click();"
|
|
||||||
} }, root);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callQueue.pop_back();
|
|
||||||
if (errorCode == 0)
|
|
||||||
return { true, S(OuterHTML) };
|
|
||||||
else if (errorCode == 1)
|
|
||||||
return { false, FormatString(L"%s", ERROR_COMMAND_FAIL) };
|
|
||||||
else if (errorCode == 2)
|
|
||||||
return { false, FormatString(L"%s: %d", ERROR_GOT_TIMEOUT, timerStop) };
|
|
||||||
else if (errorCode == 3)
|
|
||||||
return { false, FormatString(L"%s: %s", ERROR_NOTE, S(OuterHTML)) };
|
|
||||||
else if (errorCode == 4)
|
|
||||||
return { false, FormatString(L"%s (%s): %s", ERROR_LANGUAGE, S(targetLang), S(OuterHTML)) };
|
|
||||||
else if (errorCode == 5)
|
|
||||||
return { false, FormatString(L"%s: %d", ERROR_GOT_TIMEOUT, 2*timerStop) };
|
|
||||||
else if (errorCode == 6)
|
|
||||||
return { false, FormatString(L"%s", ERROR_EMPTY_ANSWER) };
|
|
||||||
else
|
|
||||||
return { false, FormatString(L"%s", TRANSLATION_ERROR) };
|
|
||||||
}
|
}
|
@ -1,233 +0,0 @@
|
|||||||
#include "qtcommon.h"
|
|
||||||
#include "extension.h"
|
|
||||||
#include "blockmarkup.h"
|
|
||||||
#include "network.h"
|
|
||||||
#include <map>
|
|
||||||
#include <fstream>
|
|
||||||
#include <QComboBox>
|
|
||||||
#include "devtools.h"
|
|
||||||
|
|
||||||
extern const char* NATIVE_LANGUAGE;
|
|
||||||
extern const char* TRANSLATE_TO;
|
|
||||||
extern const char* TRANSLATE_SELECTED_THREAD_ONLY;
|
|
||||||
extern const char* USE_TRANS_CACHE;
|
|
||||||
extern const char* MAX_SENTENCE_SIZE;
|
|
||||||
extern const char* TRANSLATION_PROVIDER;
|
|
||||||
extern QStringList languagesTo;
|
|
||||||
extern QStringList languagesFrom;
|
|
||||||
const char* TRANSLATE_FROM = u8"Translate from";
|
|
||||||
const char* NATIVE_LANGUAGE_FROM = u8"Japanese";
|
|
||||||
const char* PATH_TO_CHROME = u8"Path to Chrome";
|
|
||||||
const char* AUTO_START_CHROME = u8"Start Chrome automatically";
|
|
||||||
const char* HEADLESS_CHROME = u8"Start in headless mode";
|
|
||||||
const char* CHROME_DEBUG_PORT = u8"Chrome debug port";
|
|
||||||
const char* DEV_TOOLS_STATUS = u8"Status: ";
|
|
||||||
const char* START_DEV_TOOLS_BUTTON = u8"Start";
|
|
||||||
const char* START_DEV_TOOLS = u8"Start Chrome";
|
|
||||||
const char* STOP_DEV_TOOLS_BUTTON = u8"Stop";
|
|
||||||
const char* STOP_DEV_TOOLS = u8"Stop Chrome";
|
|
||||||
|
|
||||||
extern bool useCache, autoStartChrome, headlessChrome;
|
|
||||||
extern int maxSentenceSize, chromePort;
|
|
||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, DevTools* devTools);
|
|
||||||
|
|
||||||
const char* LANGUAGE_TO = u8"Language";
|
|
||||||
const char* LANGUAGE_FROM = u8"Language from";
|
|
||||||
const std::string TRANSLATION_CACHE_FILE = FormatString("%s Cache.txt", TRANSLATION_PROVIDER);
|
|
||||||
|
|
||||||
QFormLayout* display;
|
|
||||||
Settings settings;
|
|
||||||
Synchronized<std::wstring> translateTo = L"en", translateFrom = L"ja";
|
|
||||||
Synchronized<std::map<std::wstring, std::wstring>> translationCache;
|
|
||||||
|
|
||||||
int savedSize;
|
|
||||||
DevTools* devTools = nullptr;
|
|
||||||
std::wstring pathToChrome = L"";
|
|
||||||
|
|
||||||
void SaveCache()
|
|
||||||
{
|
|
||||||
std::wstring allTranslations(L"\xfeff");
|
|
||||||
for (const auto& [sentence, translation] : translationCache.Acquire().contents)
|
|
||||||
allTranslations.append(L"|SENTENCE|").append(sentence).append(L"|TRANSLATION|").append(translation).append(L"|END|\r\n");
|
|
||||||
std::ofstream(TRANSLATION_CACHE_FILE, std::ios::binary | std::ios::trunc).write((const char*)allTranslations.c_str(), allTranslations.size() * sizeof(wchar_t));
|
|
||||||
savedSize = translationCache->size();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EraseControlCharacters(std::wstring& text)
|
|
||||||
{
|
|
||||||
for (auto it = text.begin(); it!= text.end(); ++it)
|
|
||||||
{
|
|
||||||
if ((*it == '\n') || (*it == '\r') || (*it == '\t') || (int(*it) == 4) || (int(*it) == 5))
|
|
||||||
{
|
|
||||||
text.erase(it--);
|
|
||||||
}
|
|
||||||
if (*it == '\\' && *(it + 1) == 'n')
|
|
||||||
{
|
|
||||||
text.erase((it + 1)--);
|
|
||||||
text.erase(it--);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Window : public QDialog
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Window() :
|
|
||||||
QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
|
||||||
{
|
|
||||||
localize();
|
|
||||||
display = new QFormLayout(this);
|
|
||||||
settings.beginGroup(TRANSLATION_PROVIDER);
|
|
||||||
|
|
||||||
auto languageBoxTo = new QComboBox(this);
|
|
||||||
languageBoxTo->addItems(languagesTo);
|
|
||||||
int languageTo = -1;
|
|
||||||
if (settings.contains(LANGUAGE_TO)) languageTo = languageBoxTo->findText(settings.value(LANGUAGE_TO).toString(), Qt::MatchEndsWith);
|
|
||||||
if (languageTo < 0) languageTo = languageBoxTo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith);
|
|
||||||
if (languageTo < 0) languageTo = languageBoxTo->findText("English", Qt::MatchStartsWith);
|
|
||||||
languageBoxTo->setCurrentIndex(languageTo);
|
|
||||||
saveLanguage(languageBoxTo->currentText());
|
|
||||||
display->addRow(TRANSLATE_TO, languageBoxTo);
|
|
||||||
connect(languageBoxTo, &QComboBox::currentTextChanged, this, &Window::saveLanguage);
|
|
||||||
|
|
||||||
auto languageBoxFrom = new QComboBox(this);
|
|
||||||
languageBoxFrom->addItems(languagesFrom);
|
|
||||||
int languageFrom = -1;
|
|
||||||
if (settings.contains(LANGUAGE_FROM)) languageFrom = languageBoxFrom->findText(settings.value(LANGUAGE_FROM).toString(), Qt::MatchEndsWith);
|
|
||||||
if (languageFrom < 0) languageFrom = languageBoxFrom->findText(NATIVE_LANGUAGE_FROM, Qt::MatchStartsWith);
|
|
||||||
if (languageFrom < 0) languageFrom = languageBoxFrom->findText("Japanese", Qt::MatchStartsWith);
|
|
||||||
languageBoxFrom->setCurrentIndex(languageFrom);
|
|
||||||
saveLanguageFrom(languageBoxFrom->currentText());
|
|
||||||
display->addRow(TRANSLATE_FROM, languageBoxFrom);
|
|
||||||
connect(languageBoxFrom, &QComboBox::currentTextChanged, this, &Window::saveLanguageFrom);
|
|
||||||
for (auto [value, label] : Array<bool&, const char*>{
|
|
||||||
{ useCache, USE_TRANS_CACHE },
|
|
||||||
{ autoStartChrome, AUTO_START_CHROME },
|
|
||||||
//{ headlessChrome, HEADLESS_CHROME }
|
|
||||||
})
|
|
||||||
{
|
|
||||||
value = settings.value(label, value).toBool();
|
|
||||||
auto checkBox = new QCheckBox(this);
|
|
||||||
checkBox->setChecked(value);
|
|
||||||
display->addRow(label, checkBox);
|
|
||||||
connect(checkBox, &QCheckBox::clicked, [label, &value](bool checked) { settings.setValue(label, value = checked); });
|
|
||||||
}
|
|
||||||
for (auto [value, label] : Array<int&, const char*>{
|
|
||||||
{ maxSentenceSize, MAX_SENTENCE_SIZE },
|
|
||||||
{ chromePort, CHROME_DEBUG_PORT },
|
|
||||||
})
|
|
||||||
{
|
|
||||||
value = settings.value(label, value).toInt();
|
|
||||||
auto spinBox = new QSpinBox(this);
|
|
||||||
spinBox->setRange(0, INT_MAX);
|
|
||||||
spinBox->setValue(value);
|
|
||||||
display->addRow(label, spinBox);
|
|
||||||
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [label, &value](int newValue) { settings.setValue(label, value = newValue); });
|
|
||||||
}
|
|
||||||
|
|
||||||
auto keyInput = new QLineEdit(settings.value(PATH_TO_CHROME).toString());
|
|
||||||
pathToChrome = (S(keyInput->text()));
|
|
||||||
if (pathToChrome.empty())
|
|
||||||
{
|
|
||||||
for (auto defaultPath : Array<std::wstring>{
|
|
||||||
{ L"C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" },
|
|
||||||
{ L"C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" },
|
|
||||||
})
|
|
||||||
if (std::filesystem::exists(defaultPath))
|
|
||||||
{
|
|
||||||
pathToChrome = defaultPath;
|
|
||||||
keyInput->setText(S(pathToChrome));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
connect(keyInput, &QLineEdit::textChanged, [keyInput](QString key) { settings.setValue(PATH_TO_CHROME, S(pathToChrome = (S(key)))); });
|
|
||||||
display->addRow(PATH_TO_CHROME, keyInput);
|
|
||||||
|
|
||||||
connect(&startButton, &QPushButton::clicked, this, &Window::Start);
|
|
||||||
connect(&stopButton, &QPushButton::clicked, this, &Window::Stop);
|
|
||||||
display->addRow(START_DEV_TOOLS, &startButton);
|
|
||||||
display->addRow(STOP_DEV_TOOLS, &stopButton);
|
|
||||||
|
|
||||||
status.setFrameStyle(QFrame::Panel | QFrame::Sunken);
|
|
||||||
display->addRow(DEV_TOOLS_STATUS, &status);
|
|
||||||
|
|
||||||
setWindowTitle(TRANSLATION_PROVIDER);
|
|
||||||
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
|
||||||
|
|
||||||
std::ifstream stream(TRANSLATION_CACHE_FILE, std::ios::binary);
|
|
||||||
BlockMarkupIterator savedTranslations(stream, Array<std::wstring_view>{ L"|SENTENCE|", L"|TRANSLATION|" });
|
|
||||||
auto translationCache = ::translationCache.Acquire();
|
|
||||||
|
|
||||||
while (auto read = savedTranslations.Next())
|
|
||||||
{
|
|
||||||
auto& [sentence, translation] = read.value();
|
|
||||||
translationCache->try_emplace(std::move(sentence), std::move(translation));
|
|
||||||
}
|
|
||||||
savedSize = translationCache->size();
|
|
||||||
|
|
||||||
devTools = new DevTools(this);
|
|
||||||
connect(devTools, &DevTools::statusChanged, [this](QString text)
|
|
||||||
{
|
|
||||||
status.setText(text);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (autoStartChrome)
|
|
||||||
QMetaObject::invokeMethod(this, &Window::Start, Qt::QueuedConnection);
|
|
||||||
}
|
|
||||||
|
|
||||||
~Window()
|
|
||||||
{
|
|
||||||
Stop();
|
|
||||||
if (devTools != nullptr)
|
|
||||||
delete devTools;
|
|
||||||
SaveCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void Start()
|
|
||||||
{
|
|
||||||
if (devTools->getStatus() == "Stopped")
|
|
||||||
devTools->startDevTools(S(pathToChrome), headlessChrome, chromePort);
|
|
||||||
}
|
|
||||||
void Stop()
|
|
||||||
{
|
|
||||||
devTools->closeDevTools();
|
|
||||||
}
|
|
||||||
|
|
||||||
void saveLanguage(QString language)
|
|
||||||
{
|
|
||||||
settings.setValue(LANGUAGE_TO, S(translateTo->assign(S(language.split(": ")[1]))));
|
|
||||||
}
|
|
||||||
void saveLanguageFrom(QString language)
|
|
||||||
{
|
|
||||||
settings.setValue(LANGUAGE_FROM, S(translateFrom->assign(S(language.split(": ")[1]))));
|
|
||||||
}
|
|
||||||
QPushButton startButton{ START_DEV_TOOLS_BUTTON, this };
|
|
||||||
QPushButton stopButton{ STOP_DEV_TOOLS_BUTTON, this };
|
|
||||||
QLabel status{ "Stopped" };
|
|
||||||
} window;
|
|
||||||
|
|
||||||
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|
||||||
{
|
|
||||||
if (sentenceInfo["text number"] == 0 || sentence.size() > maxSentenceSize) return false;
|
|
||||||
|
|
||||||
bool cache = false;
|
|
||||||
std::wstring translation;
|
|
||||||
if (useCache)
|
|
||||||
{
|
|
||||||
auto translationCache = ::translationCache.Acquire();
|
|
||||||
if (auto it = translationCache->find(sentence); it != translationCache->end()) translation = it->second + L"\x200b";
|
|
||||||
}
|
|
||||||
if (translation.empty() && (sentenceInfo["current select"]))
|
|
||||||
{
|
|
||||||
EraseControlCharacters(sentence);
|
|
||||||
std::tie(cache, translation) = Translate(sentence, devTools);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cache) translationCache->try_emplace(sentence, translation);
|
|
||||||
if (cache && translationCache->size() > savedSize + 50) SaveCache();
|
|
||||||
|
|
||||||
JSON::Unescape(translation);
|
|
||||||
sentence += L"\n" + translation;
|
|
||||||
return true;
|
|
||||||
}
|
|
@ -46,7 +46,7 @@ struct PrettyWindow : QDialog
|
|||||||
{
|
{
|
||||||
PrettyWindow(const char* name)
|
PrettyWindow(const char* name)
|
||||||
{
|
{
|
||||||
localize();
|
Localize();
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
ui.display->setGraphicsEffect(&outliner);
|
ui.display->setGraphicsEffect(&outliner);
|
||||||
setWindowFlags(Qt::FramelessWindowHint);
|
setWindowFlags(Qt::FramelessWindowHint);
|
||||||
@ -439,8 +439,7 @@ private:
|
|||||||
{
|
{
|
||||||
std::vector<LookupResult> results;
|
std::vector<LookupResult> results;
|
||||||
for (auto [it, end] = std::equal_range(dictionary.begin(), dictionary.end(), DictionaryEntry{ term.toUtf8() }); it != end; ++it)
|
for (auto [it, end] = std::equal_range(dictionary.begin(), dictionary.end(), DictionaryEntry{ term.toUtf8() }); it != end; ++it)
|
||||||
if (foundDefinitions.emplace(it->definition).second)
|
if (foundDefinitions.emplace(it->definition).second) results.push_back({ term, it->definition, inflectionsUsed });
|
||||||
results.push_back({ term, it->definition, inflectionsUsed });
|
|
||||||
for (const auto& inflection : inflections) if (auto match = inflection.inflectsTo.match(term); match.hasMatch())
|
for (const auto& inflection : inflections) if (auto match = inflection.inflectsTo.match(term); match.hasMatch())
|
||||||
{
|
{
|
||||||
QStringList currentInflectionsUsed = inflectionsUsed;
|
QStringList currentInflectionsUsed = inflectionsUsed;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
#include "extension.h"
|
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
@ -123,7 +122,7 @@ QStringList languages
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true;
|
bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true;
|
||||||
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 500;
|
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
|
||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
{
|
{
|
||||||
@ -153,9 +152,11 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
|||||||
if (auto blob = Copy(JSON::Parse(httpRequest.response.substr(start))[0][2].String())) if (auto translations = Copy(JSON::Parse(blob.value())[1][0].Array()))
|
if (auto blob = Copy(JSON::Parse(httpRequest.response.substr(start))[0][2].String())) if (auto translations = Copy(JSON::Parse(blob.value())[1][0].Array()))
|
||||||
{
|
{
|
||||||
std::wstring translation;
|
std::wstring translation;
|
||||||
if (translations->size() == 1 && (translations = Copy(translations.value()[0][5].Array())))
|
if (translations->size() == 1)
|
||||||
{
|
{
|
||||||
for (const auto& sentence : translations.value()) if (sentence[0].String()) (translation += *sentence[0].String()) += L" ";
|
if (translations = Copy(translations.value()[0][5].Array()))
|
||||||
|
for (const auto& sentence : translations.value())
|
||||||
|
if (sentence[0].String()) (translation += *sentence[0].String()) += L" ";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -47,7 +47,7 @@ public:
|
|||||||
Window()
|
Window()
|
||||||
: QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
: QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
||||||
{
|
{
|
||||||
localize();
|
Localize();
|
||||||
connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript);
|
connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript);
|
||||||
|
|
||||||
if (scriptEditor.toPlainText().isEmpty()) scriptEditor.setPlainText(LUA_INTRO);
|
if (scriptEditor.toPlainText().isEmpty()) scriptEditor.setPlainText(LUA_INTRO);
|
||||||
|
@ -7,11 +7,11 @@ HttpRequest::HttpRequest(
|
|||||||
const wchar_t* objectName,
|
const wchar_t* objectName,
|
||||||
std::string body,
|
std::string body,
|
||||||
const wchar_t* headers,
|
const wchar_t* headers,
|
||||||
|
DWORD port,
|
||||||
const wchar_t* referrer,
|
const wchar_t* referrer,
|
||||||
DWORD requestFlags,
|
DWORD requestFlags,
|
||||||
const wchar_t* httpVersion,
|
const wchar_t* httpVersion,
|
||||||
const wchar_t** acceptTypes,
|
const wchar_t** acceptTypes
|
||||||
DWORD port
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
static std::atomic<HINTERNET> internet = NULL;
|
static std::atomic<HINTERNET> internet = NULL;
|
||||||
|
@ -14,11 +14,11 @@ struct HttpRequest
|
|||||||
const wchar_t* objectName,
|
const wchar_t* objectName,
|
||||||
std::string body = "",
|
std::string body = "",
|
||||||
const wchar_t* headers = NULL,
|
const wchar_t* headers = NULL,
|
||||||
|
DWORD port = INTERNET_DEFAULT_PORT,
|
||||||
const wchar_t* referrer = NULL,
|
const wchar_t* referrer = NULL,
|
||||||
DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE,
|
DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE,
|
||||||
const wchar_t* httpVersion = NULL,
|
const wchar_t* httpVersion = NULL,
|
||||||
const wchar_t** acceptTypes = NULL,
|
const wchar_t** acceptTypes = NULL
|
||||||
DWORD port = INTERNET_DEFAULT_PORT
|
|
||||||
);
|
);
|
||||||
operator bool() { return errorCode == ERROR_SUCCESS; }
|
operator bool() { return errorCode == ERROR_SUCCESS; }
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ namespace JSON
|
|||||||
std::basic_string<C> Escape(std::basic_string<C> text)
|
std::basic_string<C> Escape(std::basic_string<C> text)
|
||||||
{
|
{
|
||||||
int oldSize = text.size();
|
int oldSize = text.size();
|
||||||
text.resize(text.size() + std::count_if(text.begin(), text.end(), [](auto ch) { return ch == '\n' || ch == '\r' || ch == '\t' || ch == '\\' || ch == '"'; }));
|
text.resize(text.size() + std::count_if(text.begin(), text.end(), [](C ch) { return ch == '\n' || ch == '\r' || ch == '\t' || ch == '\\' || ch == '"'; }));
|
||||||
auto out = text.rbegin();
|
auto out = text.rbegin();
|
||||||
for (int i = oldSize - 1; i >= 0; --i)
|
for (int i = oldSize - 1; i >= 0; --i)
|
||||||
{
|
{
|
||||||
@ -60,13 +60,13 @@ namespace JSON
|
|||||||
template <typename C> struct UTF {};
|
template <typename C> struct UTF {};
|
||||||
template <> struct UTF<wchar_t>
|
template <> struct UTF<wchar_t>
|
||||||
{
|
{
|
||||||
inline static std::wstring FromCodepoint(int codepoint) { return { (wchar_t)codepoint }; } // TODO: surrogate pairs
|
inline static std::wstring FromCodepoint(unsigned codepoint) { return { (wchar_t)codepoint }; } // TODO: surrogate pairs
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename C>
|
template <typename C>
|
||||||
struct Value : private std::variant<std::monostate, std::nullopt_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>
|
struct Value : private std::variant<std::monostate, std::nullptr_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>
|
||||||
{
|
{
|
||||||
using std::variant<std::monostate, std::nullopt_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>::variant;
|
using std::variant<std::monostate, std::nullptr_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>::variant;
|
||||||
|
|
||||||
explicit operator bool() const { return index(); }
|
explicit operator bool() const { return index(); }
|
||||||
bool IsNull() const { return index() == 1; }
|
bool IsNull() const { return index() == 1; }
|
||||||
@ -78,17 +78,18 @@ namespace JSON
|
|||||||
|
|
||||||
const Value<C>& operator[](std::basic_string<C> key) const
|
const Value<C>& operator[](std::basic_string<C> key) const
|
||||||
{
|
{
|
||||||
static const Value<C> failure;
|
|
||||||
if (auto object = Object()) if (auto it = object->find(key); it != object->end()) return it->second;
|
if (auto object = Object()) if (auto it = object->find(key); it != object->end()) return it->second;
|
||||||
return failure;
|
return failure;
|
||||||
}
|
}
|
||||||
const Value<C>& operator[](int i) const
|
const Value<C>& operator[](int i) const
|
||||||
{
|
{
|
||||||
static const Value<C> failure;
|
|
||||||
if (auto array = Array()) if (i < array->size()) return array->at(i);
|
if (auto array = Array()) if (i < array->size()) return array->at(i);
|
||||||
return failure;
|
return failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const Value<C> failure;
|
||||||
};
|
};
|
||||||
|
template <typename C> const Value<C> Value<C>::failure;
|
||||||
|
|
||||||
template <typename C, int maxDepth = 25>
|
template <typename C, int maxDepth = 25>
|
||||||
Value<C> Parse(const std::basic_string<C>& text, int64_t& i, int depth)
|
Value<C> Parse(const std::basic_string<C>& text, int64_t& i, int depth)
|
||||||
@ -116,7 +117,7 @@ namespace JSON
|
|||||||
if (ch == 'u' && isxdigit(text[i + 2]) && isxdigit(text[i + 3]) && isxdigit(text[i + 4]) && isxdigit(text[i + 5]))
|
if (ch == 'u' && isxdigit(text[i + 2]) && isxdigit(text[i + 3]) && isxdigit(text[i + 4]) && isxdigit(text[i + 5]))
|
||||||
{
|
{
|
||||||
char charCode[] = { text[i + 2], text[i + 3], text[i + 4], text[i + 5], 0 };
|
char charCode[] = { text[i + 2], text[i + 3], text[i + 4], text[i + 5], 0 };
|
||||||
unescaped += UTF<C>::FromCodepoint(strtol(charCode, nullptr, 16));
|
unescaped += UTF<C>::FromCodepoint(strtoul(charCode, nullptr, 16));
|
||||||
i += 5;
|
i += 5;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -137,7 +138,7 @@ namespace JSON
|
|||||||
|
|
||||||
static C nullStr[] = { 'n', 'u', 'l', 'l' }, trueStr[] = { 't', 'r', 'u', 'e' }, falseStr[] = { 'f', 'a', 'l', 's', 'e' };
|
static C nullStr[] = { 'n', 'u', 'l', 'l' }, trueStr[] = { 't', 'r', 'u', 'e' }, falseStr[] = { 'f', 'a', 'l', 's', 'e' };
|
||||||
if (ch == nullStr[0])
|
if (ch == nullStr[0])
|
||||||
if (std::char_traits<C>::compare(text.data() + i, nullStr, std::size(nullStr)) == 0) return i += std::size(nullStr), std::nullopt;
|
if (std::char_traits<C>::compare(text.data() + i, nullStr, std::size(nullStr)) == 0) return i += std::size(nullStr), nullptr;
|
||||||
else return {};
|
else return {};
|
||||||
if (ch == trueStr[0])
|
if (ch == trueStr[0])
|
||||||
if (std::char_traits<C>::compare(text.data() + i, trueStr, std::size(trueStr)) == 0) return i += std::size(trueStr), true;
|
if (std::char_traits<C>::compare(text.data() + i, trueStr, std::size(trueStr)) == 0) return i += std::size(trueStr), true;
|
||||||
|
@ -21,7 +21,7 @@ public:
|
|||||||
Window()
|
Window()
|
||||||
: QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
: QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
||||||
{
|
{
|
||||||
localize();
|
Localize();
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
|
|
||||||
connect(ui.input, &QLineEdit::textEdited, this, &Window::setRegex);
|
connect(ui.input, &QLineEdit::textEdited, this, &Window::setRegex);
|
||||||
@ -34,7 +34,7 @@ public:
|
|||||||
void setRegex(QString regex)
|
void setRegex(QString regex)
|
||||||
{
|
{
|
||||||
ui.input->setText(regex);
|
ui.input->setText(regex);
|
||||||
std::lock_guard l(m);
|
std::scoped_lock lock(m);
|
||||||
if (!regex.isEmpty()) try { ::regex = S(regex); }
|
if (!regex.isEmpty()) try { ::regex = S(regex); }
|
||||||
catch (std::regex_error) { return ui.output->setText(INVALID_REGEX); }
|
catch (std::regex_error) { return ui.output->setText(INVALID_REGEX); }
|
||||||
else ::regex = std::nullopt;
|
else ::regex = std::nullopt;
|
||||||
|
@ -59,14 +59,12 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
{
|
{
|
||||||
std::wstring substring(sentence, suffixArray[i], commonPrefixLength);
|
std::wstring substring(sentence, suffixArray[i], commonPrefixLength);
|
||||||
bool substringCharMap[0x10000] = {};
|
bool substringCharMap[0x10000] = {};
|
||||||
for (auto ch : substring)
|
for (auto ch : substring) substringCharMap[ch] = true;
|
||||||
substringCharMap[ch] = true;
|
|
||||||
|
|
||||||
for (int regionSize = 0, j = 0; j <= sentence.size(); ++j)
|
for (int regionSize = 0, j = 0; j <= sentence.size(); ++j)
|
||||||
if (substringCharMap[sentence[j]]) regionSize += 1;
|
if (substringCharMap[sentence[j]]) regionSize += 1;
|
||||||
else if (regionSize >= commonPrefixLength * 2)
|
else if (regionSize >= commonPrefixLength * 2)
|
||||||
while (regionSize > 0)
|
while (regionSize > 0) sentence[j - regionSize--] = ERASED;
|
||||||
sentence[j - regionSize--] = ERASED;
|
|
||||||
else regionSize = 0;
|
else regionSize = 0;
|
||||||
|
|
||||||
if (!wcsstr(sentence.c_str(), substring.c_str())) std::copy(substring.begin(), substring.end(), sentence.begin() + max(suffixArray[i], suffixArray[i + 1]));
|
if (!wcsstr(sentence.c_str(), substring.c_str())) std::copy(substring.begin(), substring.end(), sentence.begin() + max(suffixArray[i], suffixArray[i + 1]));
|
||||||
|
@ -87,7 +87,7 @@ void UpdateReplacements()
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (replaceFileLastWrite.exchange(std::filesystem::last_write_time(REPLACE_SAVE_FILE)) == std::filesystem::last_write_time(REPLACE_SAVE_FILE)) return;
|
if (replaceFileLastWrite.exchange(std::filesystem::last_write_time(REPLACE_SAVE_FILE)) == std::filesystem::last_write_time(REPLACE_SAVE_FILE)) return;
|
||||||
std::scoped_lock l(m);
|
std::scoped_lock lock(m);
|
||||||
trie = Trie(std::ifstream(REPLACE_SAVE_FILE, std::ios::binary));
|
trie = Trie(std::ifstream(REPLACE_SAVE_FILE, std::ios::binary));
|
||||||
}
|
}
|
||||||
catch (std::filesystem::filesystem_error) { replaceFileLastWrite.store({}); }
|
catch (std::filesystem::filesystem_error) { replaceFileLastWrite.store({}); }
|
||||||
@ -103,7 +103,8 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
|||||||
if (trie.Empty())
|
if (trie.Empty())
|
||||||
{
|
{
|
||||||
auto file = std::ofstream(REPLACE_SAVE_FILE, std::ios::binary) << "\xff\xfe";
|
auto file = std::ofstream(REPLACE_SAVE_FILE, std::ios::binary) << "\xff\xfe";
|
||||||
for (auto ch : std::wstring_view(REPLACER_INSTRUCTIONS)) file << (ch == L'\n' ? std::string_view("\r\0\n", 4) : std::string_view((char*)&ch, 2));
|
for (auto ch : std::wstring_view(REPLACER_INSTRUCTIONS))
|
||||||
|
file << (ch == L'\n' ? std::string_view("\r\0\n", 4) : std::string_view((char*)&ch, 2));
|
||||||
_spawnlp(_P_DETACH, "notepad", "notepad", REPLACE_SAVE_FILE, NULL); // show file to user
|
_spawnlp(_P_DETACH, "notepad", "notepad", REPLACE_SAVE_FILE, NULL); // show file to user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ public:
|
|||||||
Window()
|
Window()
|
||||||
: QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
: QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
||||||
{
|
{
|
||||||
localize();
|
Localize();
|
||||||
connect(&linkButton, &QPushButton::clicked, this, &Window::Link);
|
connect(&linkButton, &QPushButton::clicked, this, &Window::Link);
|
||||||
|
|
||||||
layout.addWidget(&linkList);
|
layout.addWidget(&linkList);
|
||||||
@ -35,7 +35,7 @@ private:
|
|||||||
int to = QInputDialog::getText(this, THREAD_LINK_TO, HEXADECIMAL, QLineEdit::Normal, "", &ok3, Qt::WindowCloseButtonHint).toInt(&ok4, 16);
|
int to = QInputDialog::getText(this, THREAD_LINK_TO, HEXADECIMAL, QLineEdit::Normal, "", &ok3, Qt::WindowCloseButtonHint).toInt(&ok4, 16);
|
||||||
if (ok1 && ok2 && ok3 && ok4)
|
if (ok1 && ok2 && ok3 && ok4)
|
||||||
{
|
{
|
||||||
std::lock_guard l(m);
|
std::scoped_lock lock(m);
|
||||||
linkedTextHandles[from].insert(to);
|
linkedTextHandles[from].insert(to);
|
||||||
linkList.addItem(QString::number(from, 16) + "->" + QString::number(to, 16));
|
linkList.addItem(QString::number(from, 16) + "->" + QString::number(to, 16));
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ private:
|
|||||||
{
|
{
|
||||||
QStringList link = linkList.currentItem()->text().split("->");
|
QStringList link = linkList.currentItem()->text().split("->");
|
||||||
linkList.takeItem(linkList.currentRow());
|
linkList.takeItem(linkList.currentRow());
|
||||||
std::lock_guard l(m);
|
std::scoped_lock lock(m);
|
||||||
linkedTextHandles[link[0].toInt(nullptr, 16)].erase(link[1].toInt(nullptr, 16));
|
linkedTextHandles[link[0].toInt(nullptr, 16)].erase(link[1].toInt(nullptr, 16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,16 +32,18 @@ QFormLayout* display;
|
|||||||
Settings settings;
|
Settings settings;
|
||||||
Synchronized<std::wstring> translateTo = L"en", apiKey;
|
Synchronized<std::wstring> translateTo = L"en", apiKey;
|
||||||
|
|
||||||
Synchronized<std::map<std::wstring, std::wstring>> translationCache;
|
namespace
|
||||||
int savedSize;
|
|
||||||
|
|
||||||
void SaveCache()
|
|
||||||
{
|
{
|
||||||
std::wstring allTranslations(L"\xfeff");
|
Synchronized<std::map<std::wstring, std::wstring>> translationCache;
|
||||||
for (const auto& [sentence, translation] : translationCache.Acquire().contents)
|
int savedSize;
|
||||||
allTranslations.append(L"|SENTENCE|").append(sentence).append(L"|TRANSLATION|").append(translation).append(L"|END|\r\n");
|
void SaveCache()
|
||||||
std::ofstream(TRANSLATION_CACHE_FILE, std::ios::binary | std::ios::trunc).write((const char*)allTranslations.c_str(), allTranslations.size() * sizeof(wchar_t));
|
{
|
||||||
savedSize = translationCache->size();
|
std::wstring allTranslations(L"\xfeff");
|
||||||
|
for (const auto& [sentence, translation] : translationCache.Acquire().contents)
|
||||||
|
allTranslations.append(L"|SENTENCE|").append(sentence).append(L"|TRANSLATION|").append(translation).append(L"|END|\r\n");
|
||||||
|
std::ofstream(TRANSLATION_CACHE_FILE, std::ios::binary | std::ios::trunc).write((const char*)allTranslations.c_str(), allTranslations.size() * sizeof(wchar_t));
|
||||||
|
savedSize = translationCache->size();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Window : public QDialog
|
class Window : public QDialog
|
||||||
@ -50,7 +52,7 @@ public:
|
|||||||
Window() :
|
Window() :
|
||||||
QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
||||||
{
|
{
|
||||||
localize();
|
Localize();
|
||||||
display = new QFormLayout(this);
|
display = new QFormLayout(this);
|
||||||
|
|
||||||
settings.beginGroup(TRANSLATION_PROVIDER);
|
settings.beginGroup(TRANSLATION_PROVIDER);
|
||||||
@ -93,7 +95,7 @@ public:
|
|||||||
}
|
}
|
||||||
if (GET_API_KEY_FROM)
|
if (GET_API_KEY_FROM)
|
||||||
{
|
{
|
||||||
auto keyInput = new QLineEdit(settings.value(API_KEY).toString());
|
auto keyInput = new QLineEdit(settings.value(API_KEY).toString(), this);
|
||||||
apiKey->assign(S(keyInput->text()));
|
apiKey->assign(S(keyInput->text()));
|
||||||
QObject::connect(keyInput, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(apiKey->assign(S(key)))); });
|
QObject::connect(keyInput, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(apiKey->assign(S(key)))); });
|
||||||
auto keyLabel = new QLabel(QString("<a href=\"%1\">%2</a>").arg(GET_API_KEY_FROM, API_KEY), this);
|
auto keyLabel = new QLabel(QString("<a href=\"%1\">%2</a>").arg(GET_API_KEY_FROM, API_KEY), this);
|
||||||
@ -147,8 +149,15 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
Synchronized<std::vector<DWORD>> tokens;
|
Synchronized<std::vector<DWORD>> tokens;
|
||||||
} rateLimiter;
|
} rateLimiter;
|
||||||
|
|
||||||
|
auto StripWhitespace = [](std::wstring& text)
|
||||||
|
{
|
||||||
|
text.erase(text.begin(), std::find_if_not(text.begin(), text.end(), iswspace));
|
||||||
|
text.erase(std::find_if_not(text.rbegin(), text.rend(), iswspace).base(), text.end());
|
||||||
|
};
|
||||||
|
|
||||||
bool cache = false;
|
bool cache = false;
|
||||||
std::wstring translation;
|
std::wstring translation;
|
||||||
|
StripWhitespace(sentence);
|
||||||
if (useCache)
|
if (useCache)
|
||||||
{
|
{
|
||||||
auto translationCache = ::translationCache.Acquire();
|
auto translationCache = ::translationCache.Acquire();
|
||||||
@ -157,6 +166,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
if (translation.empty() && (!translateSelectedOnly || sentenceInfo["current select"]))
|
if (translation.empty() && (!translateSelectedOnly || sentenceInfo["current select"]))
|
||||||
if (rateLimiter.Request() || !rateLimitAll || (!rateLimitSelected && sentenceInfo["current select"])) std::tie(cache, translation) = Translate(sentence);
|
if (rateLimiter.Request() || !rateLimitAll || (!rateLimitSelected && sentenceInfo["current select"])) std::tie(cache, translation) = Translate(sentence);
|
||||||
else translation = TOO_MANY_TRANS_REQUESTS;
|
else translation = TOO_MANY_TRANS_REQUESTS;
|
||||||
|
StripWhitespace(translation);
|
||||||
if (cache) translationCache->try_emplace(sentence, translation);
|
if (cache) translationCache->try_emplace(sentence, translation);
|
||||||
if (cache && translationCache->size() > savedSize + 50) SaveCache();
|
if (cache && translationCache->size() > savedSize + 50) SaveCache();
|
||||||
|
|
||||||
|
@ -82,6 +82,8 @@ static struct // should be inline but MSVC (linker) is bugged
|
|||||||
template <typename T> operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
template <typename T> operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
||||||
} DUMMY;
|
} DUMMY;
|
||||||
|
|
||||||
|
inline auto Swallow = [](auto&&...) {};
|
||||||
|
|
||||||
template <typename T> std::optional<std::remove_cv_t<T>> Copy(T* ptr) { if (ptr) return *ptr; return {}; }
|
template <typename T> std::optional<std::remove_cv_t<T>> Copy(T* ptr) { if (ptr) return *ptr; return {}; }
|
||||||
|
|
||||||
template <typename T> inline auto FormatArg(T arg) { return arg; }
|
template <typename T> inline auto FormatArg(T arg) { return arg; }
|
||||||
@ -134,7 +136,7 @@ inline void TEXTRACTOR_MESSAGE(const wchar_t* format, const Args&... args) { Mes
|
|||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
inline void TEXTRACTOR_DEBUG(const wchar_t* format, const Args&... args) { std::thread([=] { TEXTRACTOR_MESSAGE(format, args...); }).detach(); }
|
inline void TEXTRACTOR_DEBUG(const wchar_t* format, const Args&... args) { std::thread([=] { TEXTRACTOR_MESSAGE(format, args...); }).detach(); }
|
||||||
|
|
||||||
void localize();
|
void Localize();
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
#define TEST(...) static auto _ = CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr);
|
#define TEST(...) static auto _ = CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr);
|
||||||
|
21
text.cpp
21
text.cpp
@ -22,7 +22,7 @@
|
|||||||
const char* NATIVE_LANGUAGE = "English";
|
const char* NATIVE_LANGUAGE = "English";
|
||||||
const char* ATTACH = u8"Attach to game";
|
const char* ATTACH = u8"Attach to game";
|
||||||
const char* LAUNCH = u8"Launch game";
|
const char* LAUNCH = u8"Launch game";
|
||||||
const char* GAME_CONFIG = u8"Configure game";
|
const char* CONFIG = u8"Configure game";
|
||||||
const char* DETACH = u8"Detach from game";
|
const char* DETACH = u8"Detach from game";
|
||||||
const char* FORGET = u8"Forget game";
|
const char* FORGET = u8"Forget game";
|
||||||
const char* ADD_HOOK = u8"Add hook";
|
const char* ADD_HOOK = u8"Add hook";
|
||||||
@ -144,6 +144,13 @@ const wchar_t* TOO_MANY_TRANS_REQUESTS = L"Rate limit exceeded: refuse to make m
|
|||||||
const wchar_t* TRANSLATION_ERROR = L"Error while translating";
|
const wchar_t* TRANSLATION_ERROR = L"Error while translating";
|
||||||
const char* USE_PREV_SENTENCE_CONTEXT = u8"Use previous sentence as context";
|
const char* USE_PREV_SENTENCE_CONTEXT = u8"Use previous sentence as context";
|
||||||
const char* API_KEY = u8"API key";
|
const char* API_KEY = u8"API key";
|
||||||
|
const char* CHROME_LOCATION = "Google Chrome location";
|
||||||
|
const char* START_DEVTOOLS = u8"Start DevTools";
|
||||||
|
const char* STOP_DEVTOOLS = u8"Stop DevTools";
|
||||||
|
const char* HEADLESS_MODE = u8"Headless mode";
|
||||||
|
const char* DEVTOOLS_STATUS = u8"DevTools status";
|
||||||
|
const char* AUTO_START = u8"Start automatically";
|
||||||
|
const wchar_t* ERROR_START_CHROME = L"failed to start Chrome or to connect to it";
|
||||||
const char* EXTRA_WINDOW_INFO = u8R"(Right click to change settings
|
const char* EXTRA_WINDOW_INFO = u8R"(Right click to change settings
|
||||||
Click and drag on window edges to move, or the bottom right corner to resize)";
|
Click and drag on window edges to move, or the bottom right corner to resize)";
|
||||||
const char* SENTENCE_TOO_BIG = u8"Sentence too large to display";
|
const char* SENTENCE_TOO_BIG = u8"Sentence too large to display";
|
||||||
@ -215,7 +222,7 @@ const char* THREAD_LINK_FROM = u8"Thread number to link from";
|
|||||||
const char* THREAD_LINK_TO = u8"Thread number to link to";
|
const char* THREAD_LINK_TO = u8"Thread number to link to";
|
||||||
const char* HEXADECIMAL = u8"Hexadecimal";
|
const char* HEXADECIMAL = u8"Hexadecimal";
|
||||||
|
|
||||||
void localize()
|
void Localize()
|
||||||
{
|
{
|
||||||
#ifdef TURKISH
|
#ifdef TURKISH
|
||||||
NATIVE_LANGUAGE = "Turkish";
|
NATIVE_LANGUAGE = "Turkish";
|
||||||
@ -316,7 +323,7 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
|
|||||||
NATIVE_LANGUAGE = "Chinese (simplified)";
|
NATIVE_LANGUAGE = "Chinese (simplified)";
|
||||||
ATTACH = u8"附加到游戏";
|
ATTACH = u8"附加到游戏";
|
||||||
LAUNCH = u8"启动游戏";
|
LAUNCH = u8"启动游戏";
|
||||||
GAME_CONFIG = u8"配置游戏";
|
CONFIG = u8"配置游戏";
|
||||||
DETACH = u8"从游戏分离";
|
DETACH = u8"从游戏分离";
|
||||||
FORGET = u8"移除游戏";
|
FORGET = u8"移除游戏";
|
||||||
ADD_HOOK = u8"添加钩子";
|
ADD_HOOK = u8"添加钩子";
|
||||||
@ -469,7 +476,7 @@ end)";
|
|||||||
NATIVE_LANGUAGE = "Russian";
|
NATIVE_LANGUAGE = "Russian";
|
||||||
ATTACH = u8"Присоединить к игре";
|
ATTACH = u8"Присоединить к игре";
|
||||||
LAUNCH = u8"Запустить игру";
|
LAUNCH = u8"Запустить игру";
|
||||||
GAME_CONFIG = u8"Настройки игры";
|
CONFIG = u8"Настройки игры";
|
||||||
DETACH = u8"Отсоединить от игры";
|
DETACH = u8"Отсоединить от игры";
|
||||||
FORGET = u8"Забыть игру";
|
FORGET = u8"Забыть игру";
|
||||||
ADD_HOOK = u8"Добавить хук";
|
ADD_HOOK = u8"Добавить хук";
|
||||||
@ -725,7 +732,7 @@ Klik dan tarik pinggiran jendela untuk memindahkan, atau sudut kanan bawah untuk
|
|||||||
NATIVE_LANGUAGE = "Italian";
|
NATIVE_LANGUAGE = "Italian";
|
||||||
ATTACH = u8"Collega al gioco";
|
ATTACH = u8"Collega al gioco";
|
||||||
LAUNCH = u8"Avvia gioco";
|
LAUNCH = u8"Avvia gioco";
|
||||||
GAME_CONFIG = u8"Configura gioco";
|
CONFIG = u8"Configura gioco";
|
||||||
DETACH = u8"Scollega dal gioco";
|
DETACH = u8"Scollega dal gioco";
|
||||||
FORGET = u8"Dimentica gioco";
|
FORGET = u8"Dimentica gioco";
|
||||||
ADD_HOOK = u8"Aggiungi gancio";
|
ADD_HOOK = u8"Aggiungi gancio";
|
||||||
@ -1145,7 +1152,7 @@ original_text의 빈공간은 무시되지만, replacement_text는 공백과 엔
|
|||||||
NATIVE_LANGUAGE = "French";
|
NATIVE_LANGUAGE = "French";
|
||||||
ATTACH = u8"Attacher le jeu";
|
ATTACH = u8"Attacher le jeu";
|
||||||
LAUNCH = u8"Lancer le jeu";
|
LAUNCH = u8"Lancer le jeu";
|
||||||
GAME_CONFIG = u8"Configure le jeu";
|
CONFIG = u8"Configure le jeu";
|
||||||
DETACH = u8"Detacher du jeu";
|
DETACH = u8"Detacher du jeu";
|
||||||
FORGET = u8"Oublier le jeu";
|
FORGET = u8"Oublier le jeu";
|
||||||
ADD_HOOK = u8"Ajouter un hook";
|
ADD_HOOK = u8"Ajouter un hook";
|
||||||
@ -1336,4 +1343,4 @@ Ce fichier doit être encodé en Unicode (UTF-16 Little Endian).)";
|
|||||||
#endif // FRENCH
|
#endif // FRENCH
|
||||||
};
|
};
|
||||||
|
|
||||||
static auto _ = (localize(), 0);
|
static auto _ = (Localize(), 0);
|
||||||
|
@ -35,7 +35,7 @@ namespace Engine
|
|||||||
|
|
||||||
void Hijack()
|
void Hijack()
|
||||||
{
|
{
|
||||||
static auto _ = []
|
static auto _ = ([]
|
||||||
{
|
{
|
||||||
GetModuleFileNameW(nullptr, processPath, MAX_PATH);
|
GetModuleFileNameW(nullptr, processPath, MAX_PATH);
|
||||||
processName = wcsrchr(processPath, L'\\') + 1;
|
processName = wcsrchr(processPath, L'\\') + 1;
|
||||||
@ -68,8 +68,7 @@ namespace Engine
|
|||||||
ConsoleOutput("Textractor: hijacking process located from 0x%p to 0x%p", processStartAddress, processStopAddress);
|
ConsoleOutput("Textractor: hijacking process located from 0x%p to 0x%p", processStartAddress, processStopAddress);
|
||||||
|
|
||||||
DetermineEngineType();
|
DetermineEngineType();
|
||||||
return NULL;
|
}(), 0);
|
||||||
}();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShouldMonoHook(const char* name)
|
bool ShouldMonoHook(const char* name)
|
||||||
|
@ -30,7 +30,7 @@ namespace { // unnamed
|
|||||||
0xff, 0xd3, // call ebx
|
0xff, 0xd3, // call ebx
|
||||||
0x9d, // popfd
|
0x9d, // popfd
|
||||||
0x61, // popad
|
0x61, // popad
|
||||||
0x9d, // popfd
|
0x9d, // popfd
|
||||||
0x68, 0,0,0,0, // push @original
|
0x68, 0,0,0,0, // push @original
|
||||||
0xc3 // ret ; basically absolute jmp to @original
|
0xc3 // ret ; basically absolute jmp to @original
|
||||||
};
|
};
|
||||||
@ -86,7 +86,7 @@ namespace { // unnamed
|
|||||||
0x5b, // pop rbx
|
0x5b, // pop rbx
|
||||||
0x58, // pop rax
|
0x58, // pop rax
|
||||||
0x9d, // pop rflags
|
0x9d, // pop rflags
|
||||||
0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [0] ; relative to next instruction (i.e. jmp @original)
|
0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip] ; relative to next instruction (i.e. jmp @original)
|
||||||
0,0,0,0,0,0,0,0 // @original
|
0,0,0,0,0,0,0,0 // @original
|
||||||
};
|
};
|
||||||
int this_offset = 50, send_offset = 60, original_offset = 126;
|
int this_offset = 50, send_offset = 60, original_offset = 126;
|
||||||
|
Loading…
Reference in New Issue
Block a user