diff --git a/GUI/extenwindow.cpp b/GUI/extenwindow.cpp index 157236b..07ab793 100644 --- a/GUI/extenwindow.cpp +++ b/GUI/extenwindow.cpp @@ -1,6 +1,5 @@ #include "extenwindow.h" #include "ui_extenwindow.h" -#include #include #include #include @@ -19,7 +18,7 @@ extern const char* EXTEN_WINDOW_INSTRUCTIONS; namespace { constexpr auto EXTEN_SAVE_FILE = u8"SavedExtensions.txt"; - constexpr auto DEFAULT_EXTENSIONS = u8"Remove Repeated Characters>Remove Repeated Phrases>Regex Filter>Copy to Clipboard>Bing Translate>Extra Window>Extra Newlines>Styler"; + constexpr auto DEFAULT_EXTENSIONS = u8"Remove Repeated Characters>Remove Repeated Phrases>Regex Filter>Copy to Clipboard>Google Translate>Extra Window>Extra Newlines>Styler"; struct Extension { @@ -44,7 +43,7 @@ namespace { if (auto callback = (decltype(Extension::callback))GetProcAddress(module, "OnNewSentence")) { - std::scoped_lock writeLock(extenMutex); + std::scoped_lock lock(extenMutex); extensions.push_back({ S(extenName), callback }); return true; } @@ -63,7 +62,7 @@ namespace void Reorder(QStringList extenNames) { - std::scoped_lock writeLock(extenMutex); + std::scoped_lock lock(extenMutex); std::vector extensions; for (auto extenName : extenNames) extensions.push_back(*std::find_if(::extensions.begin(), ::extensions.end(), [&](Extension extension) { return extension.name == S(extenName); })); @@ -128,7 +127,7 @@ bool DispatchSentenceToExtensions(std::wstring& sentence, const InfoForExtension void CleanupExtensions() { - std::scoped_lock writeLock(extenMutex); + std::scoped_lock lock(extenMutex); for (auto extension : extensions) FreeLibrary(GetModuleHandleW((extension.name + L".xdll").c_str())); extensions.clear(); } diff --git a/GUI/host/textthread.h b/GUI/host/textthread.h index c796561..98a9e98 100644 --- a/GUI/host/textthread.h +++ b/GUI/host/textthread.h @@ -8,8 +8,8 @@ public: using OutputCallback = bool(*)(TextThread&, std::wstring&); inline static OutputCallback Output; - inline static bool filterRepetition = true; - inline static int flushDelay = 400; // flush every 400ms by default + inline static bool filterRepetition = false; + inline static int flushDelay = 500; // flush every 500ms by default inline static int maxBufferSize = 1000; inline static int maxHistorySize = 10'000'000; diff --git a/extensions/bingtranslate.cpp b/extensions/bingtranslate.cpp index 4c9eb79..f6a3a4d 100644 --- a/extensions/bingtranslate.cpp +++ b/extensions/bingtranslate.cpp @@ -101,11 +101,16 @@ std::pair Translate(const std::wstring& text) else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) }; } + static Synchronized token; + if (token->empty()) if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"www.bing.com", L"GET", L"translator" }) + if (auto tokenPos = httpRequest.response.find(L"[" + std::to_wstring(time(nullptr) / 10)); tokenPos != std::string::npos) + token->assign(FormatString(L"&key=%s&token=%s", httpRequest.response.substr(tokenPos + 1, 13), httpRequest.response.substr(tokenPos + 16, 32))); + if (token->empty()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, L"token missing") }; if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"www.bing.com", L"POST", - FormatString(L"/ttranslatev3?fromLang=%s&to=%s&text=%s", translateFrom.Copy(), translateTo.Copy(), Escape(text)).c_str() + FormatString(L"/ttranslatev3?fromLang=%s&to=%s&text=%s%s", translateFrom.Copy(), translateTo.Copy(), Escape(text), token.Copy()).c_str() }) 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) }; diff --git a/extensions/deepltranslate.cpp b/extensions/deepltranslate.cpp index 2832ac4..0ea254c 100644 --- a/extensions/deepltranslate.cpp +++ b/extensions/deepltranslate.cpp @@ -10,17 +10,30 @@ const char* TRANSLATION_PROVIDER = "DeepL Translate"; const char* GET_API_KEY_FROM = "https://www.deepl.com/pro.html"; QStringList languages { + "Bulgarian: BG", "Chinese: ZH", + "Czech: CS", + "Danish: DA", "Dutch: NL", "English: EN", + "Estonian: ET", + "Finnish: FI", "French: FR", "German: DE", + "Greek: EL", + "Hungarian: HU", "Italian: IT", "Japanese: JA", + "Latvian: LV", + "Lithuanian: LT", "Polish: PL", "Portuguese: PT", + "Romanian: RO", "Russian: RU", + "Slovak: SK", + "Slovenian: SL", "Spanish: ES", + "Swedish: SV" }; std::wstring autoDetectLanguage = L"auto"; @@ -28,7 +41,9 @@ bool translateSelectedOnly = true, rateLimitAll = true, rateLimitSelected = true int tokenCount = 10, tokenRestoreDelay = 60000, maxSentenceSize = 1000; enum KeyType { CAT, REST }; -int keyType = CAT; +int keyType = REST; +enum PlanLevel { FREE, PAID }; +int planLevel = PAID; std::pair Translate(const std::wstring& text) { @@ -37,18 +52,25 @@ std::pair Translate(const std::wstring& text) std::string translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? "" : "&source_lang=" + WideStringToString(translateFrom.Copy()); if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", - L"api.deepl.com", + planLevel == PAID ? L"api.deepl.com" : L"api-free.deepl.com", L"POST", keyType == CAT ? L"/v1/translate" : L"/v2/translate", FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()) + translateFromComponent, L"Content-Type: application/x-www-form-urlencoded" }; httpRequest && (!httpRequest.response.empty() || (httpRequest = HttpRequest{ L"Mozilla/5.0 Textractor", - L"api.deepl.com", + planLevel == PAID ? L"api.deepl.com" : L"api-free.deepl.com", L"POST", (keyType = !keyType) == CAT ? L"/v1/translate" : L"/v2/translate", FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()) + translateFromComponent, L"Content-Type: application/x-www-form-urlencoded" + })) && (httpRequest.response.find(L"Wrong endpoint. Use") == std::string::npos || (httpRequest = HttpRequest{ + L"Mozilla/5.0 Textractor", + (planLevel = !planLevel) == PAID ? L"api.deepl.com" : L"api-free.deepl.com", + L"POST", + keyType == CAT ? L"/v1/translate" : L"/v2/translate", + FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()) + translateFromComponent, + L"Content-Type: application/x-www-form-urlencoded" }))) // Response formatted as JSON: translation starts with text":" and ends with "}] if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"translations"][0][L"text"].String())) return { true, translation.value() }; diff --git a/extensions/devtools.cpp b/extensions/devtools.cpp index e73865a..66a68b4 100644 --- a/extensions/devtools.cpp +++ b/extensions/devtools.cpp @@ -1,20 +1,40 @@ #include "devtools.h" #include #include +#include +#include #include +#include + +extern const char* CHROME_LOCATION; +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 char* TRANSLATION_PROVIDER; + +extern QFormLayout* display; +extern Settings settings; namespace { - std::function OnStatusChanged = Swallow; + auto statusLabel = new QLabel("Stopped"); PROCESS_INFORMATION processInfo = {}; std::atomic idCounter = 0; std::mutex devToolsMutex; QWebSocket webSocket; std::unordered_map>> mapQueue; + + void StatusChanged(QString status) + { + QMetaObject::invokeMethod(statusLabel, std::bind(&QLabel::setText, statusLabel, status)); + } auto _ = ([] { QObject::connect(&webSocket, &QWebSocket::stateChanged, - [](QAbstractSocket::SocketState state) { OnStatusChanged(QMetaEnum::fromType().valueToKey(state)); }); + [](QAbstractSocket::SocketState state) { StatusChanged(QMetaEnum::fromType().valueToKey(state)); }); QObject::connect(&webSocket, &QWebSocket::textMessageReceived, [](QString message) { auto result = JSON::Parse(S(message)); @@ -30,47 +50,89 @@ namespace namespace DevTools { - void Start(const std::wstring& path, std::function statusChanged, bool headless) - { - OnStatusChanged = statusChanged; - DWORD exitCode = 0; - auto args = FormatString( - L"%s --proxy-server=direct:// --disable-extensions --disable-gpu --user-data-dir=%s\\devtoolscache --remote-debugging-port=9222", - path, - std::filesystem::current_path().wstring() - ); - 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) - ) + void Start() + { + QString chromePath = settings.value(CHROME_LOCATION).toString(); + wchar_t programFiles[MAX_PATH + 100] = {}; + if (chromePath.isEmpty()) for (auto folder : { CSIDL_PROGRAM_FILESX86, CSIDL_PROGRAM_FILES, CSIDL_LOCAL_APPDATA }) { - if (HttpRequest httpRequest{ - L"Mozilla/5.0 Textractor", - L"127.0.0.1", - L"POST", - L"/json/list", - "", - NULL, - 9222, - 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& object) { return object[L"type"].String() && *object[L"type"].String() == L"page" && object[L"webSocketDebuggerUrl"].String(); } - ); it != list->end()) - { - std::scoped_lock lock(devToolsMutex); - webSocket.open(S(*(*it)[L"webSocketDebuggerUrl"].String())); - return; - } - } - OnStatusChanged("Failed Connection"); + SHGetFolderPathW(NULL, folder, NULL, SHGFP_TYPE_CURRENT, programFiles); + wcscat_s(programFiles, L"/Google/Chrome/Application/chrome.exe"); + if (std::filesystem::exists(programFiles)) chromePath = S(programFiles); } - else OnStatusChanged("Failed Startup"); + auto chromePathEdit = new QLineEdit(chromePath); + static struct : QObject + { + bool eventFilter(QObject* object, QEvent* event) + { + if (auto mouseEvent = dynamic_cast(event)) + if (mouseEvent->button() == Qt::LeftButton) + if (QString chromePath = QFileDialog::getOpenFileName(nullptr, TRANSLATION_PROVIDER, "/", "Google Chrome (*.exe)"); !chromePath.isEmpty()) + ((QLineEdit*)object)->setText(chromePath); + return false; + } + } chromeSelector; + chromePathEdit->installEventFilter(&chromeSelector); + QObject::connect(chromePathEdit, &QLineEdit::textChanged, [chromePathEdit](QString path) { settings.setValue(CHROME_LOCATION, path); }); + display->addRow(CHROME_LOCATION, chromePathEdit); + auto headlessCheck = new QCheckBox(); + auto startButton = new QPushButton(START_DEVTOOLS), stopButton = new QPushButton(STOP_DEVTOOLS); + headlessCheck->setChecked(settings.value(HEADLESS_MODE, true).toBool()); + QObject::connect(headlessCheck, &QCheckBox::clicked, [](bool headless) { settings.setValue(HEADLESS_MODE, headless); }); + QObject::connect(startButton, &QPushButton::clicked, [chromePathEdit, headlessCheck] + { + DWORD exitCode = 0; + auto args = FormatString( + L"%s --proxy-server=direct:// --disable-extensions --disable-gpu --user-data-dir=%s\\devtoolscache --remote-debugging-port=9222", + S(chromePathEdit->text()), + std::filesystem::current_path().wstring() + ); + if (headlessCheck->isChecked()) 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) + ) + { + if (HttpRequest httpRequest{ + L"Mozilla/5.0 Textractor", + L"127.0.0.1", + L"POST", + L"/json/list", + "", + NULL, + 9222, + 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& object) { return object[L"type"].String() && *object[L"type"].String() == L"page" && object[L"webSocketDebuggerUrl"].String(); } + ); it != list->end()) + { + std::scoped_lock lock(devToolsMutex); + webSocket.open(S(*(*it)[L"webSocketDebuggerUrl"].String())); + return; + } + } + StatusChanged("Failed Connection"); + } + else StatusChanged("Failed Startup"); + }); + QObject::connect(stopButton, &QPushButton::clicked, &Close); + auto buttons = new QHBoxLayout(); + buttons->addWidget(startButton); + buttons->addWidget(stopButton); + display->addRow(HEADLESS_MODE, headlessCheck); + auto autoStartCheck = new QCheckBox(); + autoStartCheck->setChecked(settings.value(AUTO_START, false).toBool()); + QObject::connect(autoStartCheck, &QCheckBox::clicked, [](bool autoStart) { settings.setValue(AUTO_START, autoStart); }); + display->addRow(AUTO_START, autoStartCheck); + display->addRow(buttons); + statusLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken); + display->addRow(DEVTOOLS_STATUS, statusLabel); + if (autoStartCheck->isChecked()) QMetaObject::invokeMethod(startButton, &QPushButton::click, Qt::QueuedConnection); } void Close() @@ -87,11 +149,14 @@ namespace DevTools CloseHandle(processInfo.hProcess); CloseHandle(processInfo.hThread); } - for (int retry = 0; ++retry < 20; Sleep(100)) { + for (int retry = 0; ++retry < 20; Sleep(100)) + { try { std::filesystem::remove_all(L"devtoolscache"); break; } catch (std::filesystem::filesystem_error) { continue; } } OnStatusChanged("Stopped"); + try { std::filesystem::remove_all(L"devtoolscache"); } catch (std::filesystem::filesystem_error) {} + StatusChanged("Stopped"); } bool Connected() diff --git a/extensions/devtools.h b/extensions/devtools.h index b6267ab..919bd7d 100644 --- a/extensions/devtools.h +++ b/extensions/devtools.h @@ -3,7 +3,7 @@ namespace DevTools { - void Start(const std::wstring& path, std::function statusChanged, bool headless); + void Start(); void Close(); bool Connected(); JSON::Value SendRequest(const char* method, const std::wstring& params = L"{}"); diff --git a/extensions/devtoolsdeepltranslate.cpp b/extensions/devtoolsdeepltranslate.cpp index e54ae9c..8c19b71 100644 --- a/extensions/devtoolsdeepltranslate.cpp +++ b/extensions/devtoolsdeepltranslate.cpp @@ -1,21 +1,10 @@ #include "qtcommon.h" #include "devtools.h" -#include -#include -#include -extern const wchar_t* TRANSLATION_ERROR; -extern const char* CHROME_LOCATION; -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; +extern const wchar_t* TRANSLATION_ERROR; extern Synchronized translateTo, translateFrom; -extern QFormLayout* display; -extern Settings settings; const char* TRANSLATION_PROVIDER = "DevTools DeepL Translate"; const char* GET_API_KEY_FROM = nullptr; @@ -24,17 +13,30 @@ int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 2500; QStringList languages { - "Chinese: zh", - "Dutch: nl", - "English: en", - "French: fr", - "German: de", - "Italian: it", - "Japanese: ja", - "Polish: pl", - "Portuguese: pt", - "Russian: ru", - "Spanish: es", + "Bulgarian: BG", + "Chinese: ZH", + "Czech: CS", + "Danish: DA", + "Dutch: NL", + "English: EN", + "Estonian: ET", + "Finnish: FI", + "French: FR", + "German: DE", + "Greek: EL", + "Hungarian: HU", + "Italian: IT", + "Japanese: JA", + "Latvian: LV", + "Lithuanian: LT", + "Polish: PL", + "Portuguese: PT", + "Romanian: RO", + "Russian: RU", + "Slovak: SK", + "Slovenian: SL", + "Spanish: ES", + "Swedish: SV" }; std::wstring autoDetectLanguage = L"auto"; @@ -44,78 +46,7 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved { case DLL_PROCESS_ATTACH: { - QString chromePath = settings.value(CHROME_LOCATION).toString(); - wchar_t programFiles[MAX_PATH + 100] = {}; - if (chromePath.isEmpty()) for (auto folder : { CSIDL_PROGRAM_FILESX86, CSIDL_PROGRAM_FILES, CSIDL_LOCAL_APPDATA }) - { - SHGetFolderPathW(NULL, folder, NULL, SHGFP_TYPE_CURRENT, programFiles); - wcscat_s(programFiles, L"/Google/Chrome/Application/chrome.exe"); - if (std::filesystem::exists(programFiles)) chromePath = S(programFiles); - } - auto chromePathEdit = new QLineEdit(chromePath); - static struct : QObject - { - bool eventFilter(QObject* object, QEvent* event) - { - if (auto mouseEvent = dynamic_cast(event)) - if (mouseEvent->button() == Qt::LeftButton) - if (QString chromePath = QFileDialog::getOpenFileName(nullptr, TRANSLATION_PROVIDER, "/", "Chrome (*.exe)"); !chromePath.isEmpty()) - ((QLineEdit*)object)->setText(chromePath); - return false; - } - } chromeSelector; - chromePathEdit->installEventFilter(&chromeSelector); - 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 headlessCheck = new QCheckBox(); - headlessCheck->setChecked(settings.value(HEADLESS_MODE, true).toBool()); - QObject::connect(headlessCheck, &QCheckBox::clicked, [](bool headless) { settings.setValue(HEADLESS_MODE, headless); }); - QObject::connect(startButton, &QPushButton::clicked, [statusLabel, chromePathEdit, headlessCheck] - { - DevTools::Start( - S(chromePathEdit->text()), - [statusLabel](QString status) - { - QMetaObject::invokeMethod(statusLabel, std::bind(&QLabel::setText, statusLabel, status)); - if (status == "ConnectedState") std::thread([] - { - if (HttpRequest httpRequest{ - L"Mozilla/5.0 Textractor", - L"127.0.0.1", - L"POST", - L"/json/version", - "", - NULL, - 9222, - 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) - DevTools::SendRequest( - "Network.setUserAgentOverride", - FormatString(LR"({"userAgent":"%s"})", userAgent->replace(userAgent->find(L"Headless"), 8, L"")) - ); - }).detach(); - }, - headlessCheck->isChecked() - ); - }); - QObject::connect(stopButton, &QPushButton::clicked, &DevTools::Close); - auto buttons = new QHBoxLayout(); - buttons->addWidget(startButton); - buttons->addWidget(stopButton); - display->addRow(HEADLESS_MODE, headlessCheck); - auto autoStartCheck = new QCheckBox(); - autoStartCheck->setChecked(settings.value(AUTO_START, false).toBool()); - QObject::connect(autoStartCheck, &QCheckBox::clicked, [](bool autoStart) { settings.setValue(AUTO_START, autoStart); }); - display->addRow(AUTO_START, autoStartCheck); - display->addRow(buttons); - statusLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken); - display->addRow(DEVTOOLS_STATUS, statusLabel); - if (autoStartCheck->isChecked()) QMetaObject::invokeMethod(startButton, &QPushButton::click, Qt::QueuedConnection); + DevTools::Start(); } break; case DLL_PROCESS_DETACH: @@ -133,7 +64,7 @@ std::pair Translate(const std::wstring& text) // DevTools can't handle concurrent translations yet static std::mutex translationMutex; std::scoped_lock lock(translationMutex); - DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/en/translator#any/%s/%s"})", translateTo.Copy(), Escape(text))); + DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/en/translator#%s/%s/%s"})", translateTo.Copy(), translateTo.Copy(), Escape(text))); if (translateFrom.Copy() != autoDetectLanguage) DevTools::SendRequest("Runtime.evaluate", FormatString(LR"({"expression":" diff --git a/extensions/regexfilter.cpp b/extensions/regexfilter.cpp index c59caf4..2ba237b 100644 --- a/extensions/regexfilter.cpp +++ b/extensions/regexfilter.cpp @@ -12,8 +12,8 @@ extern const char* CURRENT_FILTER; const char* REGEX_SAVE_FILE = "SavedRegexFilters.txt"; std::optional regex; -std::wstring replace; -std::shared_mutex m; +std::wstring replace = L"$1"; +concurrency::reader_writer_lock m; DWORD (*GetSelectedProcessId)() = nullptr; class Window : public QDialog, Localizer @@ -66,7 +66,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo) while (auto read = savedFilters.Next()) if (read->at(0) == processName) regexes.push_back(std::move(read->at(1))); if (!regexes.empty()) QMetaObject::invokeMethod(&window, std::bind(&Window::SetRegex, &window, S(regexes.back())), Qt::BlockingQueuedConnection); } - std::shared_lock lock(m); + concurrency::reader_writer_lock::scoped_lock_read readLock(m); if (regex) sentence = std::regex_replace(sentence, regex.value(), replace); return true; } diff --git a/extensions/regexfilter.ui b/extensions/regexfilter.ui index add034b..edd6e77 100644 --- a/extensions/regexfilter.ui +++ b/extensions/regexfilter.ui @@ -16,6 +16,13 @@ + + + + Save + + + @@ -28,13 +35,6 @@ - - - - Save - - - diff --git a/extensions/removerepeatphrase.cpp b/extensions/removerepeatphrase.cpp index e24a98b..a23f4d1 100644 --- a/extensions/removerepeatphrase.cpp +++ b/extensions/removerepeatphrase.cpp @@ -16,10 +16,10 @@ std::vector GenerateSuffixArray(const std::wstring& text) eqClasses[suffixArray[0]] = 0; for (int i = 1; i < text.size(); ++i) { - int currentSuffix = suffixArray[i]; - int lastSuffix = suffixArray[i - 1]; + int currentSuffix = suffixArray[i], lastSuffix = suffixArray[i - 1]; if (currentSuffix + length < text.size() && prevEqClasses[currentSuffix] == prevEqClasses[lastSuffix] && - prevEqClasses[currentSuffix + length / 2] == prevEqClasses.at(lastSuffix + length / 2)) // not completely certain that this will stay in range + prevEqClasses[currentSuffix + length / 2] == prevEqClasses[lastSuffix + length / 2] + ) eqClasses[currentSuffix] = eqClasses[lastSuffix]; else eqClasses[currentSuffix] = i; } diff --git a/extensions/replacer.cpp b/extensions/replacer.cpp index 03ae8dc..6d5fa33 100644 --- a/extensions/replacer.cpp +++ b/extensions/replacer.cpp @@ -10,7 +10,7 @@ extern const wchar_t* REPLACER_INSTRUCTIONS; constexpr auto REPLACE_SAVE_FILE = u8"SavedReplacements.txt"; std::atomic replaceFileLastWrite = {}; -std::shared_mutex m; +concurrency::reader_writer_lock m; class Trie { @@ -121,7 +121,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo) { UpdateReplacements(); - std::shared_lock lock(m); + concurrency::reader_writer_lock::scoped_lock_read readLock(m); sentence = trie.Replace(sentence); return true; } diff --git a/extensions/threadlinker.cpp b/extensions/threadlinker.cpp index 3f69000..0fc5230 100644 --- a/extensions/threadlinker.cpp +++ b/extensions/threadlinker.cpp @@ -4,12 +4,13 @@ extern const char* THREAD_LINKER; extern const char* LINK; +extern const char* UNLINK; extern const char* THREAD_LINK_FROM; extern const char* THREAD_LINK_TO; extern const char* HEXADECIMAL; -std::unordered_map> linkedTextHandles; -std::shared_mutex m; +std::unordered_map> linkedTextHandles; +concurrency::reader_writer_lock m; class Window : public QDialog, Localizer { @@ -17,9 +18,14 @@ public: Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint) { connect(&linkButton, &QPushButton::clicked, this, &Window::Link); + connect(&unlinkButton, &QPushButton::clicked, this, &Window::Unlink); layout.addWidget(&linkList); - layout.addWidget(&linkButton); + layout.addLayout(&buttons); + buttons.addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); + buttons.addWidget(&linkButton); + buttons.addWidget(&unlinkButton); + buttons.addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding)); setWindowTitle(THREAD_LINKER); QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection); @@ -34,14 +40,13 @@ private: if (ok1 && ok2 && ok3 && ok4) { std::scoped_lock lock(m); - linkedTextHandles[from].insert(to); - linkList.addItem(QString::number(from, 16) + "->" + QString::number(to, 16)); + if (linkedTextHandles[from].insert(to).second) linkList.addItem(QString::number(from, 16) + "->" + QString::number(to, 16)); } } - void keyPressEvent(QKeyEvent* event) override + void Unlink() { - if (event->key() == Qt::Key_Delete && linkList.currentItem()) + if (linkList.currentItem()) { QStringList link = linkList.currentItem()->text().split("->"); linkList.takeItem(linkList.currentRow()); @@ -50,18 +55,22 @@ private: } } + void keyPressEvent(QKeyEvent* event) override + { + if (event->key() == Qt::Key_Delete) Unlink(); + } + QHBoxLayout layout{ this }; + QVBoxLayout buttons; QListWidget linkList{ this }; - QPushButton linkButton{ LINK, this }; + QPushButton linkButton{ LINK, this }, unlinkButton{ UNLINK, this }; } window; bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo) { - std::shared_lock lock(m); - int64_t textHandle = sentenceInfo["text number"]; - - for (auto linkedHandle : linkedTextHandles[textHandle]) - ((void(*)(int64_t, const wchar_t*))sentenceInfo["void (*AddText)(int64_t number, const wchar_t* text)"])(linkedHandle, sentence.c_str()); - + concurrency::reader_writer_lock::scoped_lock_read readLock(m); + auto links = linkedTextHandles.find(sentenceInfo["text number"]); + if (links != linkedTextHandles.end()) for (auto link : links->second) + ((void(*)(int64_t, const wchar_t*))sentenceInfo["void (*AddText)(int64_t number, const wchar_t* text)"])(link, sentence.c_str()); return false; } diff --git a/include/common.h b/include/common.h index 32e6d0a..ec37f27 100644 --- a/include/common.h +++ b/include/common.h @@ -2,6 +2,7 @@ #define WIN32_LEAN_AND_MEAN #include +#include #include #include #include @@ -15,7 +16,6 @@ #include #include #include -#include #include #include #include diff --git a/text.cpp b/text.cpp index 6686a3e..fc9ca82 100644 --- a/text.cpp +++ b/text.cpp @@ -221,6 +221,7 @@ Whitespace in original_text is ignored, but replacement_text can contain spaces, This file must be encoded in Unicode (UTF-16 Little Endian).)"; const char* THREAD_LINKER = u8"Thread Linker"; const char* LINK = u8"Link"; +const char* UNLINK = u8"Unlink"; const char* THREAD_LINK_FROM = u8"Thread number to link from"; const char* THREAD_LINK_TO = u8"Thread number to link to"; const char* HEXADECIMAL = u8"Hexadecimal"; diff --git a/texthook/engine/engine.cc b/texthook/engine/engine.cc index baf6503..f5052b2 100644 --- a/texthook/engine/engine.cc +++ b/texthook/engine/engine.cc @@ -16912,8 +16912,14 @@ bool InsertRenpyHook() hp.offset = 4; hp.index = 0xc; hp.length_offset = 0; - hp.split = pusha_ebx_off - 4; - hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT | USING_SPLIT; + //hp.split = pusha_ebx_off - 4; + hp.text_fun = [](auto, auto, auto, DWORD* data, DWORD* split, DWORD* count) + { + *data = *(DWORD*)(*data + 0xc); + *count = wcslen((wchar_t*)*data) * sizeof(wchar_t); + *split = wcschr((wchar_t*)*data, L'%') == nullptr; + }; + hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT/* | USING_SPLIT*/; //hp.filter_fun = [](void* str, auto, auto, auto) { return *(wchar_t*)str != L'%'; }; NewHook(hp, "Ren'py"); return true; diff --git a/texthook/texthook.h b/texthook/texthook.h index f0b9ccd..db3b728 100644 --- a/texthook/texthook.h +++ b/texthook/texthook.h @@ -51,6 +51,6 @@ private: }; -enum { MAX_HOOK = 300, HOOK_BUFFER_SIZE = MAX_HOOK * sizeof(TextHook), HOOK_SECTION_SIZE = HOOK_BUFFER_SIZE * 2 }; +enum { MAX_HOOK = 2500, HOOK_BUFFER_SIZE = MAX_HOOK * sizeof(TextHook), HOOK_SECTION_SIZE = HOOK_BUFFER_SIZE * 2 }; // EOF