Compare commits

...

57 Commits

Author SHA1 Message Date
8ad82f0467 Merge remote-tracking branch 'hentaitaku/dev' 2024-09-05 13:00:19 +08:00
287a0365b9 Merge remote-tracking branch 'Blu3train/fixDevtoolsdeepltranslateWrongTargetLanguageSelection' 2024-09-05 12:42:16 +08:00
b03b94da4f Merge remote-tracking branch 'DDWSdwqdq/patch-1' 2024-09-05 12:42:02 +08:00
1ad5668c8e Merge remote-tracking branch 'Blu3train/extrawindow_autohide' 2024-09-05 12:41:18 +08:00
80bf2f0039 Merge remote-tracking branch 'Blu3train/extension_remove_repeated_leading_sentence' 2024-09-05 12:39:55 +08:00
bdf06b518b Merge remote-tracking branch 'Blu3train/cmdline_paramete_clipboard_selection_at_startup' 2024-09-05 12:39:45 +08:00
4d1dfdac85 Merge remote-tracking branch 'Blu3train/translatewrapper_regex_lineedit_dont_call_translator' 2024-09-05 12:39:30 +08:00
fad9fb3d51 Merge remote-tracking branch 'Blu3train/yandexTranslateExtension' 2024-09-05 12:38:37 +08:00
a7f5dde13b Merge remote-tracking branch 'Blu3train/devtools_systran_translate_fix' 2024-09-05 12:38:26 +08:00
d53202e553 Merge remote-tracking branch 'Blu3train/replacerTranslatedTextExtension' 2024-09-05 12:38:16 +08:00
Shoaib Shakeel
ff5ea30230 WillPlus3 hook (#18)
Fixed By [lgztx96](https://github.com/Artikash/Textractor/pull/880)

Co-authored-by: lgztx <z2337878191@outlook.com>
2024-08-14 17:39:13 +02:00
Shoaib Shakeel
1f7d8d77a8 Add Unityx86, Mono, WOLFRPG and Godot engine hook support (#17)
example-game: [NoMeme] PHOENIXES

Fix By [DDWSdwqdq](https://github.com/Artikash/Textractor/pull/834)

Co-authored-by: DDWSdwqdq <53893117+DDWSdwqdq@users.noreply.github.com>
2024-08-14 17:39:13 +02:00
DDWSdwqdq
1e93ceafc3 add EntisGLS hook 2024-08-14 17:35:30 +02:00
DDWSdwqdq
db399976d5 Update engine.cc 2024-08-14 17:35:30 +02:00
DDWSdwqdq
8447192880 add anim hook 2024-08-14 17:35:30 +02:00
DDWSdwqdq
4dbf0742a4 add Anim HOOK 2024-08-14 17:35:30 +02:00
DDWSdwqdq
840934678e add unity_x86 mono HOOK,WOLFRPG HOOK. 2024-08-14 17:35:30 +02:00
DDWSdwqdq
ed563ebb86 Unity_add_HOOK
Unity_add_HOOK
2024-08-14 17:35:30 +02:00
DDWSdwqdq
1369937ef5 Godot Engine ,add hook
example-game: [NoMeme] PHOENIXES
2024-08-14 17:35:30 +02:00
DDWSdwqdq
9dedecf7f4 add EntisGLS hook 2024-08-14 17:33:16 +02:00
DDWSdwqdq
9d00d56582 Update engine.cc 2024-08-14 17:33:16 +02:00
DDWSdwqdq
7a55c35006 add anim hook 2024-08-14 17:33:16 +02:00
DDWSdwqdq
7d2fb06e82 add Anim HOOK 2024-08-14 17:33:16 +02:00
DDWSdwqdq
5da847e06a add unity_x86 mono HOOK,WOLFRPG HOOK. 2024-08-14 17:33:16 +02:00
DDWSdwqdq
36201b9ff3 Unity_add_HOOK
Unity_add_HOOK
2024-08-14 17:33:16 +02:00
Akash Mozumdar
54be169714 change qt version to make alpha builds compatible with releases 2024-08-14 17:33:16 +02:00
DDWSdwqdq
9f6bb23540 Godot Engine ,add hook
example-game: [NoMeme] PHOENIXES
2024-08-14 17:33:16 +02:00
Blu3train
916b2c87b6 DeepL selector traslation text fix 2023-09-27 18:57:36 +02:00
Blu3train
c6b52b6886 DeepL selector traslation text fix 2023-05-16 20:35:45 +02:00
Blu3train
da56952c61 DeepL changed the way to select secondary language (US English and UK English) 2023-04-04 01:32:07 +02:00
Blu3train
cb43f0db01 DeepL changed the way to select secondary language (US English and UK English) 2023-04-04 01:12:30 +02:00
Blu3train
786e0aac05 Added Korean and Norwegian languages selection 2023-02-21 22:30:00 +01:00
Blu3train
72c6916c02 Replacer Translated Text Extension 2022-10-10 00:05:13 +02:00
Blu3train
fcf1ea2f14 Added Ukrainian language selection 2022-09-19 23:45:10 +02:00
Blu3train
441baaf8ee auto resize bug fixed 2022-08-21 15:43:01 +02:00
Blu3train
0ed1e6b378 code cleanup 2022-08-16 00:13:46 +02:00
Blu3train
ac55b984cb DevTools Systran Translate fix 2022-08-12 22:01:22 +02:00
Blu3train
9826aa06e2 changed default 2022-08-06 01:55:52 +02:00
Blu3train
0e9d3a9085 Last modification removed because I didn't realize that it's managed by the flag 2022-08-06 01:31:26 +02:00
Akash Mozumdar
df53e31119 Merge branch 'master' into pr/757-devtools-deepl-translate-sometimes-the-target-language-is-wrong 2022-07-27 00:34:27 -04:00
Blu3train
265af2f79a code cleanup 2022-07-22 18:20:32 +02:00
Blu3train
a9f3ff9644 Yandex Translate Extension 2022-07-22 18:05:10 +02:00
Blu3train
78e3be549e Fixed closure drop-down language selection due to site modification 2022-07-19 18:33:49 +02:00
Blu3train
5777dc3db1 bug fixed 2022-06-30 20:26:58 +02:00
Blu3train
560c1a9a05 Added Indonesian language selection 2022-06-12 10:35:50 +02:00
Blu3train
3d042c29c8 Added Turkish language selection 2022-06-04 20:07:02 +02:00
Blu3train
66b3c9a023 translates only the selected thread 2022-06-02 15:58:20 +02:00
Blu3train
24a199a8c4 translatewrapper: regex lineedit doesn't translate sentence on a full match 2022-05-24 23:33:21 +02:00
Blu3train
85f9d73868 cmdline parameter -c clipboard selection at startup 2022-05-22 15:52:43 +02:00
Blu3train
4eeb747694 code cleanup 2022-05-18 00:33:08 +02:00
Blu3train
8977d515c4 Extension: Remove Repeated Leading Sentence 2022-05-18 00:21:55 +02:00
Blu3train
92dcaa391c added setWindowTitle to recognize extension in task manager 2022-05-11 20:09:47 +02:00
Blu3train
08750b3309 changed default time to 0 for disabling it on first use 2022-04-30 11:18:35 +02:00
Blu3train
d0d454df24 Extra Window: added auto hide text after timeout 2022-04-19 23:01:33 +02:00
Blu3train
7cc8d3871e some characters are in Html encoded format in the translated text 2022-04-19 19:11:07 +02:00
DDWSdwqdq
66fbff28ba
Update engine.cc 2022-03-29 09:30:10 +08:00
Blu3train
88de8b1f5c DevTools DeepL Translate: sometimes the target language selection is wrong 2022-02-16 21:49:08 +01:00
19 changed files with 1065 additions and 74 deletions

View File

@ -688,11 +688,14 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
std::unique_ptr<LPWSTR[], Functor<LocalFree>> argv(CommandLineToArgvW(GetCommandLineW(), &argc)); std::unique_ptr<LPWSTR[], Functor<LocalFree>> argv(CommandLineToArgvW(GetCommandLineW(), &argc));
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 = wcstoul(arg.substr(2).c_str(), nullptr, 0)) 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);
if (arg[1] == L'c' || arg[1] == L'C')
ViewThread(1);
}
std::thread([] { for (; ; Sleep(10000)) AttachSavedProcesses(); }).detach(); std::thread([] { for (; ; Sleep(10000)) AttachSavedProcesses(); }).detach();
} }

View File

@ -55,12 +55,15 @@ foreach ($language in @{
"Regex Replacer", "Regex Replacer",
"Regex Replacer Translated Text", "Regex Replacer Translated Text",
"Remove Repeated Characters", "Remove Repeated Characters",
"Remove Repeated Leading Sentence",
"Remove Repeated Phrases", "Remove Repeated Phrases",
"Remove Repeated Phrases 2", "Remove Repeated Phrases 2",
"Remove 30 Repeated Sentences", "Remove 30 Repeated Sentences",
"Replacer", "Replacer",
"Replacer Translated Text",
"Styler", "Styler",
"Thread Linker" "Thread Linker",
"Yandex Translate"
)) ))
{ {
copy -Force -Recurse -Verbose -Destination "$folder/$arch/$extension.xdll" -Path "Release_$arch/$extension.dll"; copy -Force -Recurse -Verbose -Destination "$folder/$arch/$extension.xdll" -Path "Release_$arch/$extension.dll";

View File

@ -14,12 +14,15 @@ add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp)
add_library(Regex\ Replacer MODULE regexreplacer.cpp extensionimpl.cpp) add_library(Regex\ Replacer MODULE regexreplacer.cpp extensionimpl.cpp)
add_library(Regex\ Replacer\ Translated\ Text MODULE regexreplacertranslatedtext.cpp extensionimpl.cpp) add_library(Regex\ Replacer\ Translated\ Text MODULE regexreplacertranslatedtext.cpp extensionimpl.cpp)
add_library(Remove\ Repeated\ Characters MODULE removerepeatchar.cpp extensionimpl.cpp) add_library(Remove\ Repeated\ Characters MODULE removerepeatchar.cpp extensionimpl.cpp)
add_library(Remove\ Repeated\ Leading\ Sentence MODULE removerepeatedleadingsentence.cpp extensionimpl.cpp)
add_library(Remove\ Repeated\ Phrases MODULE removerepeatphrase.cpp extensionimpl.cpp) add_library(Remove\ Repeated\ Phrases MODULE removerepeatphrase.cpp extensionimpl.cpp)
add_library(Remove\ Repeated\ Phrases\ 2 MODULE removerepeatphrase2.cpp extensionimpl.cpp) add_library(Remove\ Repeated\ Phrases\ 2 MODULE removerepeatphrase2.cpp extensionimpl.cpp)
add_library(Remove\ 30\ Repeated\ Sentences MODULE removerepeatsentence.cpp extensionimpl.cpp) add_library(Remove\ 30\ Repeated\ Sentences MODULE removerepeatsentence.cpp extensionimpl.cpp)
add_library(Replacer MODULE replacer.cpp extensionimpl.cpp) add_library(Replacer MODULE replacer.cpp extensionimpl.cpp)
add_library(Replacer\ Translated\ Text MODULE replacertranslatedtext.cpp extensionimpl.cpp)
add_library(Styler MODULE styler.cpp extensionimpl.cpp) add_library(Styler MODULE styler.cpp extensionimpl.cpp)
add_library(Thread\ Linker MODULE threadlinker.cpp extensionimpl.cpp) add_library(Thread\ Linker MODULE threadlinker.cpp extensionimpl.cpp)
add_library(Yandex\ Translate MODULE yandextranslate.cpp translatewrapper.cpp network.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)
@ -35,12 +38,15 @@ target_precompile_headers(Regex\ Filter REUSE_FROM pch)
target_precompile_headers(Regex\ Replacer REUSE_FROM pch) target_precompile_headers(Regex\ Replacer REUSE_FROM pch)
target_precompile_headers(Regex\ Replacer\ Translated\ Text REUSE_FROM pch) target_precompile_headers(Regex\ Replacer\ Translated\ Text REUSE_FROM pch)
target_precompile_headers(Remove\ Repeated\ Characters REUSE_FROM pch) target_precompile_headers(Remove\ Repeated\ Characters REUSE_FROM pch)
target_precompile_headers(Remove\ Repeated\ Leading\ Sentence REUSE_FROM pch)
target_precompile_headers(Remove\ Repeated\ Phrases REUSE_FROM pch) target_precompile_headers(Remove\ Repeated\ Phrases REUSE_FROM pch)
target_precompile_headers(Remove\ Repeated\ Phrases\ 2 REUSE_FROM pch) target_precompile_headers(Remove\ Repeated\ Phrases\ 2 REUSE_FROM pch)
target_precompile_headers(Remove\ 30\ Repeated\ Sentences REUSE_FROM pch) target_precompile_headers(Remove\ 30\ Repeated\ Sentences REUSE_FROM pch)
target_precompile_headers(Replacer REUSE_FROM pch) target_precompile_headers(Replacer REUSE_FROM pch)
target_precompile_headers(Replacer\ Translated\ Text REUSE_FROM pch)
target_precompile_headers(Styler REUSE_FROM pch) target_precompile_headers(Styler REUSE_FROM pch)
target_precompile_headers(Thread\ Linker REUSE_FROM pch) target_precompile_headers(Thread\ Linker REUSE_FROM pch)
target_precompile_headers(Yandex\ Translate 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)
@ -53,6 +59,7 @@ target_link_libraries(Lua lua53 Qt5::Widgets)
target_link_libraries(Regex\ Filter Qt5::Widgets) target_link_libraries(Regex\ Filter Qt5::Widgets)
target_link_libraries(Styler Qt5::Widgets) target_link_libraries(Styler Qt5::Widgets)
target_link_libraries(Thread\ Linker Qt5::Widgets) target_link_libraries(Thread\ Linker Qt5::Widgets)
target_link_libraries(Yandex\ Translate winhttp Qt5::Widgets)
add_custom_target(Cleaner ALL COMMAND del *.xdll WORKING_DIRECTORY ${CMAKE_FINAL_OUTPUT_DIRECTORY}) add_custom_target(Cleaner ALL COMMAND del *.xdll WORKING_DIRECTORY ${CMAKE_FINAL_OUTPUT_DIRECTORY})

View File

@ -194,6 +194,7 @@ extern const std::unordered_map<std::wstring, std::wstring> codes
bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true; bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000; int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000;
std::wstring dontTranslateIfMatch = L"";
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp) std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{ {

View File

@ -105,6 +105,7 @@ extern const std::unordered_map<std::wstring, std::wstring> codes
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = true, useCache = true, useFilter = true; bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = true, useCache = true, useFilter = true;
int tokenCount = 10, rateLimitTimespan = 60000, maxSentenceSize = 1000; int tokenCount = 10, rateLimitTimespan = 60000, maxSentenceSize = 1000;
std::wstring dontTranslateIfMatch = L"";
enum KeyType { CAT, REST }; enum KeyType { CAT, REST };
int keyType = REST; int keyType = REST;

View File

@ -7,6 +7,7 @@ extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "DevTools DeepL Translate"; const char* TRANSLATION_PROVIDER = "DevTools DeepL Translate";
const char* GET_API_KEY_FROM = nullptr; const char* GET_API_KEY_FROM = nullptr;
std::wstring currTranslateTo;
extern const QStringList languagesTo extern const QStringList languagesTo
{ {
@ -23,10 +24,13 @@ extern const QStringList languagesTo
"German", "German",
"Greek", "Greek",
"Hungarian", "Hungarian",
"Indonesian",
"Italian", "Italian",
"Japanese", "Japanese",
"Korean",
"Latvian", "Latvian",
"Lithuanian", "Lithuanian",
"Norwegian",
"Polish", "Polish",
"Portuguese", "Portuguese",
"Portuguese (Brazilian)", "Portuguese (Brazilian)",
@ -35,7 +39,9 @@ extern const QStringList languagesTo
"Slovak", "Slovak",
"Slovenian", "Slovenian",
"Spanish", "Spanish",
"Swedish" "Swedish",
"Turkish",
"Ukrainian"
}, },
languagesFrom = languagesFrom =
{ {
@ -51,10 +57,13 @@ languagesFrom =
"German", "German",
"Greek", "Greek",
"Hungarian", "Hungarian",
"Indonesian",
"Italian", "Italian",
"Japanese", "Japanese",
"Korean",
"Latvian", "Latvian",
"Lithuanian", "Lithuanian",
"Norwegian",
"Polish", "Polish",
"Portuguese", "Portuguese",
"Romanian", "Romanian",
@ -62,43 +71,51 @@ languagesFrom =
"Slovak", "Slovak",
"Slovenian", "Slovenian",
"Spanish", "Spanish",
"Swedish" "Swedish",
"Turkish",
"Ukrainian"
}; };
extern const std::unordered_map<std::wstring, std::wstring> codes extern const std::unordered_map<std::wstring, std::wstring> codes
{ {
{ { L"Bulgarian" }, { L"Bulgarian" } }, { { L"Bulgarian" }, { L"bg-BG" } },
{ { L"Chinese" }, { L"Chinese" } }, { { L"Chinese" }, { L"zh" } },
{ { L"Chinese (Simplified)" }, { L"Chinese (simplified)" } }, { { L"Chinese (Simplified)" }, { L"zh-ZH" } },
{ { L"Czech" }, { L"Czech" } }, { { L"Czech" }, { L"cs-CS" } },
{ { L"Danish" }, { L"Danish" } }, { { L"Danish" }, { L"da-DA" } },
{ { L"Dutch" }, { L"Dutch" } }, { { L"Dutch" }, { L"nl-NL" } },
{ { L"English" }, { L"English" } }, { { L"English" }, { L"en" } },
{ { L"English (American)" }, { L"English (American)" } }, { { L"English (American)" }, { L"en-US" } },
{ { L"English (British)" }, { L"English (British)" } }, { { L"English (British)" }, { L"en-GB" } },
{ { L"Estonian" }, { L"Estonian" } }, { { L"Estonian" }, { L"et-ET" } },
{ { L"Finnish" }, { L"Finnish" } }, { { L"Finnish" }, { L"fi-FI" } },
{ { L"French" }, { L"French" } }, { { L"French" }, { L"fr-FR" } },
{ { L"German" }, { L"German" } }, { { L"German" }, { L"de-DE" } },
{ { L"Greek" }, { L"Greek" } }, { { L"Greek" }, { L"el-EL" } },
{ { L"Hungarian" }, { L"Hungarian" } }, { { L"Hungarian" }, { L"hu-HU" } },
{ { L"Italian" }, { L"Italian" } }, { { L"Indonesian" }, { L"id-ID" } },
{ { L"Japanese" }, { L"Japanese" } }, { { L"Italian" }, { L"it-IT" } },
{ { L"Latvian" }, { L"Latvian" } }, { { L"Japanese" }, { L"ja-JA" } },
{ { L"Lithuanian" }, { L"Lithuanian" } }, { { L"Korean" }, { L"ko-KO" } },
{ { L"Polish" }, { L"Polish" } }, { { L"Latvian" }, { L"lv-LV" } },
{ { L"Portuguese" }, { L"Portuguese" } }, { { L"Lithuanian" }, { L"lt-LT" } },
{ { L"Portuguese (Brazilian)" }, { L"Portuguese (Brazilian)" } }, { { L"Norwegian" }, { L"nb-NB" } },
{ { L"Romanian" }, { L"Romanian" } }, { { L"Polish" }, { L"pl-PL" } },
{ { L"Russian" }, { L"Russian" } }, { { L"Portuguese" }, { L"pt-PT" } },
{ { L"Slovak" }, { L"Slovak" } }, { { L"Portuguese (Brazilian)" }, { L"pt-BR" } },
{ { L"Slovenian" }, { L"Slovenian" } }, { { L"Romanian" }, { L"ro-RO" } },
{ { L"Spanish" }, { L"Spanish" } }, { { L"Russian" }, { L"ru-RU" } },
{ { L"Swedish" }, { L"Swedish" } }, { { L"Slovak" }, { L"sk-SK" } },
{ { L"?" }, { L"Detect language" } } { { L"Slovenian" }, { L"sl-SL" } },
{ { L"Spanish" }, { L"es-ES" } },
{ { L"Swedish" }, { L"sv-SV" } },
{ { L"Turkish" }, { L"tr-TR" } },
{ { L"Ukrainian" }, { L"uk-UK" } },
{ { L"?" }, { L"auto" } }
}; };
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true; bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500; int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500;
std::wstring dontTranslateIfMatch = L"";
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{ {
@ -118,6 +135,21 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
return TRUE; return TRUE;
} }
std::wstring htmlDecode (std::wstring text) {
const std::wstring enc[] = { L"&amp;", L"&lt;", L"&gt;" };
const std::wstring dec[] = { L"&", L"<", L">" };
size_t pos;
for(int j = 0; j < 3; j++) {
do {
pos = text.find(enc[j]);
if (pos != std::wstring::npos)
text.replace (pos,enc[j].length(),dec[j]);
} while (pos != std::wstring::npos);
}
return text;
}
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp) std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{ {
if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) }; if (!DevTools::Connected()) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, ERROR_START_CHROME) };
@ -126,21 +158,18 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationPar
std::scoped_lock lock(translationMutex); std::scoped_lock lock(translationMutex);
std::wstring escaped; // DeepL breaks with slash in input std::wstring escaped; // DeepL breaks with slash in input
for (auto ch : text) ch == '/' ? escaped += L"\\/" : escaped += ch; for (auto ch : text) ch == '/' ? escaped += L"\\/" : escaped += ch;
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/en/translator#en/en/%s"})", Escape(escaped))); if (currTranslateTo == tlp.translateTo)
for (int retry = 0; ++retry < 20; Sleep(100)) DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/en/translator#%s/%s/%s"})", (tlp.translateFrom == L"?") ? codes.at(tlp.translateFrom) : codes.at(tlp.translateFrom).substr(0, 2), codes.at(tlp.translateTo).substr(0, 2), Escape(escaped)));
if (Copy(DevTools::SendRequest("Runtime.evaluate", LR"({"expression":"document.readyState"})")[L"result"][L"value"].String()) == L"complete") break; else
{
DevTools::SendRequest("Runtime.evaluate", FormatString(LR"({"expression":" currTranslateTo = tlp.translateTo;
document.querySelector('.lmt__language_select--source').querySelector('button').click(); DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/en/translator#%s/%s/%s"})", (tlp.translateFrom == L"?") ? codes.at(tlp.translateFrom) : codes.at(tlp.translateFrom).substr(0, 2), codes.at(tlp.translateTo), Escape(escaped)));
document.evaluate(`//*[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click(); }
document.querySelector('.lmt__language_select--target').querySelector('button').click();
document.evaluate(`//*[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click();
"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo)));
for (int retry = 0; ++retry < 100; Sleep(100)) for (int retry = 0; ++retry < 100; Sleep(100))
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate", if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
LR"({"expression":"document.querySelector('#target-dummydiv').innerHTML.trim() ","returnByValue":true})" LR"({"expression":"document.querySelector('[data-testid=translator-target-input]').textContent.trim() ","returnByValue":true})"
)[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() }; )[L"result"][L"value"].String())) if (!translation->empty()) return { true, htmlDecode(translation.value()) };
if (auto errorMessage = Copy(DevTools::SendRequest("Runtime.evaluate", if (auto errorMessage = Copy(DevTools::SendRequest("Runtime.evaluate",
LR"({"expression":"document.querySelector('div.lmt__system_notification').innerHTML","returnByValue":true})" LR"({"expression":"document.querySelector('div.lmt__system_notification').innerHTML","returnByValue":true})"
)[L"result"][L"value"].String())) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, errorMessage.value()) }; )[L"result"][L"value"].String())) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, errorMessage.value()) };

View File

@ -48,6 +48,7 @@ extern const std::unordered_map<std::wstring, std::wstring> codes
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true; bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500; int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500;
std::wstring dontTranslateIfMatch = L"";
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{ {

View File

@ -7,6 +7,7 @@ extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "DevTools Systran Translate"; const char* TRANSLATION_PROVIDER = "DevTools Systran Translate";
const char* GET_API_KEY_FROM = nullptr; const char* GET_API_KEY_FROM = nullptr;
bool firstTranslation = true;
extern const QStringList languagesTo extern const QStringList languagesTo
{ {
@ -114,6 +115,7 @@ extern const std::unordered_map<std::wstring, std::wstring> codes
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true; bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500; int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500;
std::wstring dontTranslateIfMatch = L"";
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{ {
@ -140,11 +142,20 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationPar
static std::mutex translationMutex; static std::mutex translationMutex;
std::scoped_lock lock(translationMutex); std::scoped_lock lock(translationMutex);
if (firstTranslation) {
firstTranslation = false;
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.systran.net/en/translate/?source=%s&target=%s&input=%s"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text)));
for (int retry = 0; ++retry < 100; Sleep(100))
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
LR"({"expression":"document.querySelector('#yDmH0d button').innerHTML.trim() ","returnByValue":true})"
)[L"result"][L"value"].String())) if (!translation->empty()) break;
}
DevTools::SendRequest( DevTools::SendRequest(
"Page.navigate", "Page.navigate",
FormatString(LR"({"url":"https://translate.systran.net/?source=%s&target=%s&input=%s"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text)) FormatString(LR"({"url":"https://www.systran.net/en/translate/?source=%s&target=%s&input=%s"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text))
); );
for (int retry = 0; ++retry < 100; Sleep(100)) for (int retry = 0; ++retry < 150; Sleep(100))
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate", if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
LR"({"expression":"document.querySelector('#outputEditor').textContent.trim() ","returnByValue":true})" LR"({"expression":"document.querySelector('#outputEditor').textContent.trim() ","returnByValue":true})"
)[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() }; )[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() };

View File

@ -15,6 +15,7 @@
#include <QWheelEvent> #include <QWheelEvent>
#include <QScrollArea> #include <QScrollArea>
#include <QAbstractNativeEventFilter> #include <QAbstractNativeEventFilter>
#include <QTimer>
extern const char* EXTRA_WINDOW_INFO; extern const char* EXTRA_WINDOW_INFO;
extern const char* TOPMOST; extern const char* TOPMOST;
@ -27,6 +28,7 @@ extern const char* CENTERED_TEXT;
extern const char* AUTO_RESIZE_WINDOW_HEIGHT; extern const char* AUTO_RESIZE_WINDOW_HEIGHT;
extern const char* CLICK_THROUGH; extern const char* CLICK_THROUGH;
extern const char* HIDE_MOUSEOVER; extern const char* HIDE_MOUSEOVER;
extern const char* HIDE_TEXT;
extern const char* DICTIONARY; extern const char* DICTIONARY;
extern const char* DICTIONARY_INSTRUCTIONS; extern const char* DICTIONARY_INSTRUCTIONS;
extern const char* BG_COLOR; extern const char* BG_COLOR;
@ -36,9 +38,16 @@ extern const char* OUTLINE_COLOR;
extern const char* OUTLINE_SIZE; extern const char* OUTLINE_SIZE;
extern const char* OUTLINE_SIZE_INFO; extern const char* OUTLINE_SIZE_INFO;
extern const char* FONT; extern const char* FONT;
extern const char* TIMER_HIDE_TEXT;
extern const char* TEXT_TIMEOUT;
extern const char* TEXT_TIMEOUT_ADD_PER_CHAR;
constexpr auto DICTIONARY_SAVE_FILE = u8"SavedDictionary.txt"; constexpr auto DICTIONARY_SAVE_FILE = u8"SavedDictionary.txt";
constexpr int CLICK_THROUGH_HOTKEY = 0xc0d0; constexpr int CLICK_THROUGH_HOTKEY = 0xc0d0;
constexpr int HIDE_TEXT_HOTKEY = 0xc0d1;
const qreal COLOR_ALFAF_HIDE_WINDOW = 0.05;
const int TEXT_TIMEOUT_DEF = 0;
const int TEXT_TIMEOUT_ADD_PER_CHAR_DEF = 0;
QColor colorPrompt(QWidget* parent, QColor default, const QString& title, bool customOpacity = true) QColor colorPrompt(QWidget* parent, QColor default, const QString& title, bool customOpacity = true)
{ {
@ -49,12 +58,17 @@ QColor colorPrompt(QWidget* parent, QColor default, const QString& title, bool c
struct PrettyWindow : QDialog, Localizer struct PrettyWindow : QDialog, Localizer
{ {
QAction *hideTextAction;
QTimer *timerHideText = new QTimer(this);
int text_timeout, text_timeout_per_char;
PrettyWindow(const char* name) PrettyWindow(const char* name)
{ {
ui.setupUi(this); ui.setupUi(this);
ui.display->setGraphicsEffect(outliner = new Outliner); ui.display->setGraphicsEffect(outliner = new Outliner);
setWindowFlags(Qt::FramelessWindowHint); setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground); setAttribute(Qt::WA_TranslucentBackground);
setWindowTitle("Extra Window");
settings.beginGroup(name); settings.beginGroup(name);
QFont font = ui.display->font(); QFont font = ui.display->font();
@ -64,18 +78,24 @@ struct PrettyWindow : QDialog, Localizer
outliner->color = settings.value(OUTLINE_COLOR, outliner->color).value<QColor>(); outliner->color = settings.value(OUTLINE_COLOR, outliner->color).value<QColor>();
outliner->size = settings.value(OUTLINE_SIZE, outliner->size).toDouble(); outliner->size = settings.value(OUTLINE_SIZE, outliner->size).toDouble();
autoHide = settings.value(HIDE_MOUSEOVER, autoHide).toBool(); autoHide = settings.value(HIDE_MOUSEOVER, autoHide).toBool();
text_timeout = settings.value(TEXT_TIMEOUT, TEXT_TIMEOUT_DEF).toInt();
text_timeout_per_char = settings.value(TEXT_TIMEOUT_ADD_PER_CHAR, TEXT_TIMEOUT_ADD_PER_CHAR_DEF).toInt();
menu.addAction(FONT, this, &PrettyWindow::RequestFont); menu.addAction(FONT, this, &PrettyWindow::RequestFont);
menu.addAction(BG_COLOR, [this] { SetBackgroundColor(colorPrompt(this, backgroundColor, BG_COLOR)); }); menu.addAction(BG_COLOR, [this] { if (hideText) ToggleHideText(); SetBackgroundColor(colorPrompt(this, backgroundColor, BG_COLOR)); });
menu.addAction(TEXT_COLOR, [this] { SetTextColor(colorPrompt(this, TextColor(), TEXT_COLOR)); }); menu.addAction(TEXT_COLOR, [this] { if (hideText) ToggleHideText(); SetTextColor(colorPrompt(this, TextColor(), TEXT_COLOR)); });
QAction* outlineAction = menu.addAction(TEXT_OUTLINE, this, &PrettyWindow::SetOutline); QAction* outlineAction = menu.addAction(TEXT_OUTLINE, this, &PrettyWindow::SetOutline);
outlineAction->setCheckable(true); outlineAction->setCheckable(true);
outlineAction->setChecked(outliner->size >= 0); outlineAction->setChecked(outliner->size >= 0);
QAction* autoHideAction = menu.addAction(HIDE_MOUSEOVER, this, [this](bool autoHide) { settings.setValue(HIDE_MOUSEOVER, this->autoHide = autoHide); }); menu.addAction(TIMER_HIDE_TEXT, this, &PrettyWindow::SetTimerHideText);
QAction* autoHideAction = menu.addAction(HIDE_MOUSEOVER, this, &PrettyWindow::SetHideMouseover);
autoHideAction->setCheckable(true); autoHideAction->setCheckable(true);
autoHideAction->setChecked(autoHide); autoHideAction->setChecked(autoHide);
connect(this, &QDialog::customContextMenuRequested, [this](QPoint point) { menu.exec(mapToGlobal(point)); }); connect(this, &QDialog::customContextMenuRequested, [this](QPoint point) { menu.exec(mapToGlobal(point)); });
connect(ui.display, &QLabel::customContextMenuRequested, [this](QPoint point) { menu.exec(ui.display->mapToGlobal(point)); }); connect(ui.display, &QLabel::customContextMenuRequested, [this](QPoint point) { menu.exec(ui.display->mapToGlobal(point)); });
startTimer(50); startTimer(50);
timerHideText->setSingleShot(true);
connect(timerHideText, &QTimer::timeout, [=]() {on_timeoutHideText();});
} }
~PrettyWindow() ~PrettyWindow()
@ -85,29 +105,55 @@ struct PrettyWindow : QDialog, Localizer
Ui::ExtraWindow ui; Ui::ExtraWindow ui;
protected: protected:
bool hidden = false, autoHide = false, hideText=false;
qreal backgroundColorAlphaF = COLOR_ALFAF_HIDE_WINDOW;
void SetBackgroundColorHideText(bool hideText)
{
if (hideText)
{
if (settings.value(BG_COLOR).value<QColor>().alpha() > backgroundColorAlphaF) backgroundColor.setAlphaF(backgroundColorAlphaF);
if (settings.value(OUTLINE_COLOR).value<QColor>().alpha() > backgroundColorAlphaF) outliner->color.setAlphaF(backgroundColorAlphaF);
QColor hiddenTextColor = TextColor();
if (settings.value(TEXT_COLOR).value<QColor>().alpha() > backgroundColorAlphaF) hiddenTextColor.setAlphaF(backgroundColorAlphaF);
ui.display->setPalette(QPalette(hiddenTextColor, {}, {}, {}, {}, {}, {}));
repaint();
}
else
{
backgroundColor.setAlpha(settings.value(BG_COLOR).value<QColor>().alpha());
outliner->color.setAlpha(settings.value(OUTLINE_COLOR).value<QColor>().alpha());
ui.display->setPalette(QPalette(settings.value(TEXT_COLOR).value<QColor>(), {}, {}, {}, {}, {}, {}));
repaint();
}
}
void ToggleHideText()
{
if (!autoHide)
{
hideText = !hideText;
SetBackgroundColorHideText(hideText);
}
};
void timerEvent(QTimerEvent*) override void timerEvent(QTimerEvent*) override
{ {
if (autoHide && geometry().contains(QCursor::pos())) if (autoHide && geometry().contains(QCursor::pos()))
{ {
if (!hidden) if (!hidden)
{ {
if (backgroundColor.alphaF() > 0.05) backgroundColor.setAlphaF(0.05);
if (outliner->color.alphaF() > 0.05) outliner->color.setAlphaF(0.05);
QColor hiddenTextColor = TextColor();
if (hiddenTextColor.alphaF() > 0.05) hiddenTextColor.setAlphaF(0.05);
ui.display->setPalette(QPalette(hiddenTextColor, {}, {}, {}, {}, {}, {}));
hidden = true; hidden = true;
repaint(); SetBackgroundColorHideText(true);
} }
} }
else if (hidden) else if (hidden)
{ {
backgroundColor.setAlpha(settings.value(BG_COLOR).value<QColor>().alpha());
outliner->color.setAlpha(settings.value(OUTLINE_COLOR).value<QColor>().alpha());
ui.display->setPalette(QPalette(settings.value(TEXT_COLOR).value<QColor>(), {}, {}, {}, {}, {}, {}));
hidden = false; hidden = false;
repaint(); SetBackgroundColorHideText(false);
} }
} }
@ -115,6 +161,12 @@ protected:
Settings settings{ this }; Settings settings{ this };
private: private:
void on_timeoutHideText()
{
if (!hideText)
ToggleHideText();
}
void RequestFont() void RequestFont()
{ {
if (QFont font = QFontDialog::getFont(&ok, ui.display->font(), this, FONT); ok) if (QFont font = QFontDialog::getFont(&ok, ui.display->font(), this, FONT); ok)
@ -147,6 +199,8 @@ private:
void SetOutline(bool enable) void SetOutline(bool enable)
{ {
if (hideText)
ToggleHideText();
if (enable) if (enable)
{ {
QColor color = colorPrompt(this, outliner->color, OUTLINE_COLOR); QColor color = colorPrompt(this, outliner->color, OUTLINE_COLOR);
@ -158,12 +212,28 @@ private:
settings.setValue(OUTLINE_SIZE, outliner->size); settings.setValue(OUTLINE_SIZE, outliner->size);
} }
void SetHideMouseover(bool autoHide)
{
if (hideText)
ToggleHideText();
settings.setValue(HIDE_MOUSEOVER, this->autoHide = autoHide);
hideTextAction->setDisabled(autoHide);
};
void SetTimerHideText()
{
text_timeout = QInputDialog::getInt(this, TIMER_HIDE_TEXT, TEXT_TIMEOUT, text_timeout, 0, INT_MAX, 2, nullptr, Qt::WindowCloseButtonHint);
text_timeout_per_char = QInputDialog::getInt(this, TIMER_HIDE_TEXT, TEXT_TIMEOUT_ADD_PER_CHAR, text_timeout_per_char, 0, INT_MAX, 2, nullptr, Qt::WindowCloseButtonHint);
settings.setValue(TEXT_TIMEOUT, text_timeout);
settings.setValue(TEXT_TIMEOUT_ADD_PER_CHAR, text_timeout_per_char);
};
void paintEvent(QPaintEvent*) override void paintEvent(QPaintEvent*) override
{ {
QPainter(this).fillRect(rect(), backgroundColor); QPainter(this).fillRect(rect(), backgroundColor);
} }
bool autoHide = false, hidden = false;
QColor backgroundColor{ palette().window().color() }; QColor backgroundColor{ palette().window().color() };
struct Outliner : QGraphicsEffect struct Outliner : QGraphicsEffect
{ {
@ -214,6 +284,7 @@ public:
action->setChecked(default); action->setChecked(default);
} }
hideTextAction = menu.addAction(HIDE_TEXT, this, &ExtraWindow::ToggleHideText);
menu.addAction(CLICK_THROUGH, this, &ExtraWindow::ToggleClickThrough); menu.addAction(CLICK_THROUGH, this, &ExtraWindow::ToggleClickThrough);
ui.display->installEventFilter(this); ui.display->installEventFilter(this);
@ -221,6 +292,7 @@ public:
QMetaObject::invokeMethod(this, [this] QMetaObject::invokeMethod(this, [this]
{ {
RegisterHotKey((HWND)winId(), HIDE_TEXT_HOTKEY, MOD_ALT | MOD_NOREPEAT, 0x54);
RegisterHotKey((HWND)winId(), CLICK_THROUGH_HOTKEY, MOD_ALT | MOD_NOREPEAT, 0x58); RegisterHotKey((HWND)winId(), CLICK_THROUGH_HOTKEY, MOD_ALT | MOD_NOREPEAT, 0x58);
show(); show();
AddSentence(EXTRA_WINDOW_INFO); AddSentence(EXTRA_WINDOW_INFO);
@ -229,6 +301,7 @@ public:
~ExtraWindow() ~ExtraWindow()
{ {
AddSentence(EXTRA_WINDOW_INFO);
settings.setValue(WINDOW, geometry()); settings.setValue(WINDOW, geometry());
} }
@ -273,6 +346,10 @@ private:
resize(width(), height() - ui.display->height() + resize(width(), height() - ui.display->height() +
QFontMetrics(ui.display->font(), ui.display).boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence).height() QFontMetrics(ui.display->font(), ui.display).boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence).height()
); );
if (hideText)
ToggleHideText();
if (text_timeout > 0 && !autoHide)
timerHideText->start(text_timeout+sentence.size()*text_timeout_per_char);
} }
void SetTopmost(bool topmost) void SetTopmost(bool topmost)
@ -341,6 +418,8 @@ private:
else exStyle &= ~WS_EX_TRANSPARENT; else exStyle &= ~WS_EX_TRANSPARENT;
SetWindowLongPtrW((HWND)window, GWL_EXSTYLE, exStyle); SetWindowLongPtrW((HWND)window, GWL_EXSTYLE, exStyle);
} }
backgroundColorAlphaF = clickThrough ? 0.0 : COLOR_ALFAF_HIDE_WINDOW;
SetBackgroundColorHideText(hidden || hideText);
}; };
void ShowDictionary(QPoint mouse) void ShowDictionary(QPoint mouse)
@ -385,7 +464,10 @@ private:
{ {
auto msg = (MSG*)message; auto msg = (MSG*)message;
if (msg->message == WM_HOTKEY) if (msg->message == WM_HOTKEY)
{
if (msg->wParam == HIDE_TEXT_HOTKEY) return ToggleHideText(), true;
if (msg->wParam == CLICK_THROUGH_HOTKEY) return ToggleClickThrough(), true; if (msg->wParam == CLICK_THROUGH_HOTKEY) return ToggleClickThrough(), true;
}
return false; return false;
} }

View File

@ -234,6 +234,7 @@ extern const std::unordered_map<std::wstring, std::wstring> codes
bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true; bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000; int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000;
std::wstring dontTranslateIfMatch = L"";
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp) std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{ {

View File

@ -0,0 +1,26 @@
#include "extension.h"
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (!sentenceInfo["current select"] || sentenceInfo["text number"] == 0) return false;
static std::wstring prevSentence;
std::wstring checkSentence = prevSentence;
prevSentence = sentence;
if (sentence.substr(0, checkSentence.size()) == checkSentence)
{
auto Ltrim = [](std::wstring& text)
{
text.erase(text.begin(), std::find_if_not(text.begin(), text.end(), iswspace));
};
sentence = sentence.substr(checkSentence.size());
Ltrim(sentence);
return true;
}
return false;
}

View File

@ -0,0 +1,151 @@
#include "extension.h"
#include "blockmarkup.h"
#include <cwctype>
#include <fstream>
#include <sstream>
#include <process.h>
extern const wchar_t* REPLACER_INSTRUCTIONS;
constexpr auto REPLACE_SAVE_FILE = u8"SavedReplacementsTranslatedText.txt";
std::atomic<std::filesystem::file_time_type> replaceFileLastWrite = {};
concurrency::reader_writer_lock m;
class Trie
{
public:
Trie(const std::istream& replacementScript)
{
BlockMarkupIterator replacementScriptParser(replacementScript, Array<std::wstring_view>{ L"|ORIG|", L"|BECOMES|" });
while (auto read = replacementScriptParser.Next())
{
const auto& [original, replacement] = read.value();
Node* current = &root;
for (auto ch : original) if (!Ignore(ch)) current = Next(current, ch);
if (current != &root)
current->value = charStorage.insert(charStorage.end(), replacement.c_str(), replacement.c_str() + replacement.size() + 1) - charStorage.begin();
}
}
std::wstring Replace(const std::wstring& sentence) const
{
std::wstring result;
for (int i = 0; i < sentence.size();)
{
std::wstring_view replacement(sentence.c_str() + i, 1);
int originalLength = 1;
const Node* current = &root;
for (int j = i; current && j <= sentence.size(); ++j)
{
if (current->value >= 0)
{
replacement = charStorage.data() + current->value;
originalLength = j - i;
}
if (!Ignore(sentence[j])) current = Next(current, sentence[j]) ? Next(current, sentence[j]) : Next(current, L'^');
}
result += replacement;
i += originalLength;
}
return result;
}
bool Empty()
{
return root.charMap.empty();
}
private:
static bool Ignore(wchar_t ch)
{
return ch <= 0x20 || iswspace(ch);
}
template <typename Node>
static Node* Next(Node* node, wchar_t ch)
{
auto it = std::lower_bound(node->charMap.begin(), node->charMap.end(), ch, [](const auto& one, auto two) { return one.first < two; });
if (it != node->charMap.end() && it->first == ch) return it->second.get();
if constexpr (!std::is_const_v<Node>) return node->charMap.insert(it, { ch, std::make_unique<Node>() })->second.get();
return nullptr;
}
struct Node
{
std::vector<std::pair<wchar_t, std::unique_ptr<Node>>> charMap;
ptrdiff_t value = -1;
} root;
std::vector<wchar_t> charStorage;
} trie = { std::istringstream("") };
void UpdateReplacements()
{
try
{
if (replaceFileLastWrite.exchange(std::filesystem::last_write_time(REPLACE_SAVE_FILE)) == std::filesystem::last_write_time(REPLACE_SAVE_FILE)) return;
std::scoped_lock lock(m);
trie = Trie(std::ifstream(REPLACE_SAVE_FILE, std::ios::binary));
}
catch (std::filesystem::filesystem_error) { replaceFileLastWrite.store({}); }
}
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
UpdateReplacements();
if (trie.Empty())
{
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));
SpawnThread([] { _spawnlp(_P_DETACH, "notepad", "notepad", REPLACE_SAVE_FILE, NULL); }); // show file to user
}
}
break;
case DLL_PROCESS_DETACH:
{
}
break;
}
return TRUE;
}
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{
if (!sentenceInfo["current select"] || sentenceInfo["text number"] == 0) return false;
size_t posTranslation = sentence.find(L"\x200b \n");
if (posTranslation == std::wstring::npos) return false;
posTranslation +=3;
UpdateReplacements();
std::wstring sTranslated = sentence.substr(posTranslation, std::wstring::npos);
sentence = sentence.substr(0, posTranslation);
concurrency::reader_writer_lock::scoped_lock_read readLock(m);
sentence += trie.Replace(sTranslated);
return true;
}
TEST(
{
std::wstring replacementScript = LR"(
|ORIG||BECOMES|goodbye |END|Ignore this text
And this text   
|ORIG||BECOMES|idiot|END|
|ORIG| |BECOMES| hello|END||ORIG|delet^this|BECOMES||END|)";
Trie replacements(std::istringstream(std::string{ (const char*)replacementScript.c_str(), replacementScript.size() * sizeof(wchar_t) }));
std::wstring original = LR"(Don't replace this 
delete this)";
std::wstring replaced = Trie(std::move(replacements)).Replace(original);
assert(replaced == L"Don't replace thisgoodbye idiot hello");
}
);

View File

@ -17,6 +17,7 @@ extern const char* FILTER_GARBAGE;
extern const char* MAX_TRANSLATIONS_IN_TIMESPAN; extern const char* MAX_TRANSLATIONS_IN_TIMESPAN;
extern const char* TIMESPAN; extern const char* TIMESPAN;
extern const char* MAX_SENTENCE_SIZE; extern const char* MAX_SENTENCE_SIZE;
extern const char* DONT_TRANSLATE_IF_MATCH;
extern const char* API_KEY; extern const char* API_KEY;
extern const wchar_t* SENTENCE_TOO_LARGE_TO_TRANS; extern const wchar_t* SENTENCE_TOO_LARGE_TO_TRANS;
extern const wchar_t* TRANSLATION_ERROR; extern const wchar_t* TRANSLATION_ERROR;
@ -27,6 +28,7 @@ extern const char* GET_API_KEY_FROM;
extern const QStringList languagesTo, languagesFrom; extern const QStringList languagesTo, languagesFrom;
extern bool translateSelectedOnly, useRateLimiter, rateLimitSelected, useCache, useFilter; extern bool translateSelectedOnly, useRateLimiter, rateLimitSelected, useCache, useFilter;
extern int tokenCount, rateLimitTimespan, maxSentenceSize; extern int tokenCount, rateLimitTimespan, maxSentenceSize;
extern std::wstring dontTranslateIfMatch;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp); std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp);
QFormLayout* display; QFormLayout* display;
@ -118,6 +120,14 @@ public:
display->addRow(label, spinBox); display->addRow(label, spinBox);
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [label, &value](int newValue) { settings.setValue(label, value = newValue); }); connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [label, &value](int newValue) { settings.setValue(label, value = newValue); });
} }
auto matchEdit = new QLineEdit(settings.value(DONT_TRANSLATE_IF_MATCH).toString(), this);
dontTranslateIfMatch = S(matchEdit->text());
QObject::connect(matchEdit, &QLineEdit::textChanged, [](QString match) { settings.setValue(DONT_TRANSLATE_IF_MATCH, S(dontTranslateIfMatch = S(match))); });
auto matchLabel = new QLabel(QString("%1 (<a href=\"https://regexr.com/\">regex</a>)").arg(DONT_TRANSLATE_IF_MATCH), this);
matchLabel->setOpenExternalLinks(true);
display->addRow(matchLabel, matchEdit);
if (GET_API_KEY_FROM) if (GET_API_KEY_FROM)
{ {
auto keyEdit = new QLineEdit(settings.value(API_KEY).toString(), this); auto keyEdit = new QLineEdit(settings.value(API_KEY).toString(), this);
@ -182,6 +192,16 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
sentence.erase(std::remove_if(sentence.begin(), sentence.end(), [](wchar_t ch) { return ch < ' ' && ch != '\n'; }), sentence.end()); sentence.erase(std::remove_if(sentence.begin(), sentence.end(), [](wchar_t ch) { return ch < ' ' && ch != '\n'; }), sentence.end());
} }
if (sentence.empty()) return true; if (sentence.empty()) return true;
try
{
if (!dontTranslateIfMatch.empty() && std::regex_match(sentence, std::wregex(dontTranslateIfMatch)))
{
sentence += L"\x200b \n" + sentence;
return true;
}
} catch (...) {}
if (sentence.size() > maxSentenceSize) translation = SENTENCE_TOO_LARGE_TO_TRANS; if (sentence.size() > maxSentenceSize) translation = SENTENCE_TOO_LARGE_TO_TRANS;
if (useCache) if (useCache)
{ {

View File

@ -0,0 +1,238 @@
#include "qtcommon.h"
#include "translatewrapper.h"
#include "network.h"
extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "Yandex Translate";
const char* GET_API_KEY_FROM = nullptr;
extern const QStringList languagesTo
{
"Afrikaans",
"Albanian",
"Amharic",
"Arabic",
"Armenian",
"Azerbaijani",
"Bashkir",
"Basque",
"Belarusian",
"Bengali",
"Bosnian",
"Bulgarian",
"Burmese",
"Catalan",
"Cebuano",
"Chinese",
"Chuvash",
"Croatian",
"Czech",
"Danish",
"Dutch",
"Elvish (Sindarin)",
"Emoji",
"English",
"Esperanto",
"Estonian",
"Finnish",
"French",
"Galician",
"Georgian",
"German",
"Greek",
"Gujarati",
"Haitian",
"Hebrew",
"Hill Mari",
"Hindi",
"Hungarian",
"Icelandic",
"Indonesian",
"Irish",
"Italian",
"Japanese",
"Javanese",
"Kannada",
"Kazakh",
"Kazakh (Latin)",
"Khmer",
"Korean",
"Kyrgyz",
"Lao",
"Latin",
"Latvian",
"Lithuanian",
"Luxembourgish",
"Macedonian",
"Malagasy",
"Malay",
"Malayalam",
"Maltese",
"Maori",
"Marathi",
"Mari",
"Mongolian",
"Nepali",
"Norwegian",
"Papiamento",
"Persian",
"Polish",
"Portuguese",
"Punjabi",
"Romanian",
"Russian",
"Scottish Gaelic",
"Serbian",
"Sinhalese",
"Slovak",
"Slovenian",
"Spanish",
"Sundanese",
"Swahili",
"Swedish",
"Tagalog",
"Tajik",
"Tamil",
"Tatar",
"Telugu",
"Thai",
"Turkish",
"Udmurt",
"Ukrainian",
"Urdu",
"Uzbek",
"Uzbek (Cyrillic)",
"Vietnamese",
"Welsh",
"Xhosa",
"Yakut",
"Yiddish",
"Zulu"
}, languagesFrom = languagesTo;
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Afrikaans" }, { L"af" } },
{ { L"Albanian" }, { L"sq" } },
{ { L"Amharic" }, { L"am" } },
{ { L"Arabic" }, { L"ar" } },
{ { L"Armenian" }, { L"hy" } },
{ { L"Azerbaijani" }, { L"az" } },
{ { L"Bashkir" }, { L"ba" } },
{ { L"Basque" }, { L"eu" } },
{ { L"Belarusian" }, { L"be" } },
{ { L"Bengali" }, { L"bn" } },
{ { L"Bosnian" }, { L"bs" } },
{ { L"Bulgarian" }, { L"bg" } },
{ { L"Burmese" }, { L"my" } },
{ { L"Catalan" }, { L"ca" } },
{ { L"Cebuano" }, { L"ceb" } },
{ { L"Chinese" }, { L"zh" } },
{ { L"Chuvash" }, { L"cv" } },
{ { L"Croatian" }, { L"hr" } },
{ { L"Czech" }, { L"cs" } },
{ { L"Danish" }, { L"da" } },
{ { L"Dutch" }, { L"nl" } },
{ { L"Elvish (Sindarin)" }, { L"sjn" } },
{ { L"Emoji" }, { L"emj" } },
{ { L"English" }, { L"en" } },
{ { L"Esperanto" }, { L"eo" } },
{ { L"Estonian" }, { L"et" } },
{ { L"Finnish" }, { L"fi" } },
{ { L"French" }, { L"fr" } },
{ { L"Galician" }, { L"gl" } },
{ { L"Georgian" }, { L"ka" } },
{ { L"German" }, { L"de" } },
{ { L"Greek" }, { L"el" } },
{ { L"Gujarati" }, { L"gu" } },
{ { L"Haitian" }, { L"ht" } },
{ { L"Hebrew" }, { L"he" } },
{ { L"Hill Mari" }, { L"mrj" } },
{ { L"Hindi" }, { L"hi" } },
{ { L"Hungarian" }, { L"hu" } },
{ { L"Icelandic" }, { L"is" } },
{ { L"Indonesian" }, { L"id" } },
{ { L"Irish" }, { L"ga" } },
{ { L"Italian" }, { L"it" } },
{ { L"Japanese" }, { L"ja" } },
{ { L"Javanese" }, { L"jv" } },
{ { L"Kannada" }, { L"kn" } },
{ { L"Kazakh" }, { L"kk" } },
{ { L"Kazakh (Latin)" }, { L"kazlat" } },
{ { L"Khmer" }, { L"km" } },
{ { L"Korean" }, { L"ko" } },
{ { L"Kyrgyz" }, { L"ky" } },
{ { L"Lao" }, { L"lo" } },
{ { L"Latin" }, { L"la" } },
{ { L"Latvian" }, { L"lv" } },
{ { L"Lithuanian" }, { L"lt" } },
{ { L"Luxembourgish" }, { L"lb" } },
{ { L"Macedonian" }, { L"mk" } },
{ { L"Malagasy" }, { L"mg" } },
{ { L"Malay" }, { L"ms" } },
{ { L"Malayalam" }, { L"ml" } },
{ { L"Maltese" }, { L"mt" } },
{ { L"Maori" }, { L"mi" } },
{ { L"Marathi" }, { L"mr" } },
{ { L"Mari" }, { L"mhr" } },
{ { L"Mongolian" }, { L"mn" } },
{ { L"Nepali" }, { L"ne" } },
{ { L"Norwegian" }, { L"no" } },
{ { L"Papiamento" }, { L"pap" } },
{ { L"Persian" }, { L"fa" } },
{ { L"Polish" }, { L"pl" } },
{ { L"Portuguese" }, { L"pt" } },
{ { L"Punjabi" }, { L"pa" } },
{ { L"Romanian" }, { L"ro" } },
{ { L"Russian" }, { L"ru" } },
{ { L"Scottish Gaelic" }, { L"gd" } },
{ { L"Serbian" }, { L"sr" } },
{ { L"Sinhalese" }, { L"si" } },
{ { L"Slovak" }, { L"sk" } },
{ { L"Slovenian" }, { L"sl" } },
{ { L"Spanish" }, { L"es" } },
{ { L"Sundanese" }, { L"su" } },
{ { L"Swahili" }, { L"sw" } },
{ { L"Swedish" }, { L"sv" } },
{ { L"Tagalog" }, { L"tl" } },
{ { L"Tajik" }, { L"tg" } },
{ { L"Tamil" }, { L"ta" } },
{ { L"Tatar" }, { L"tt" } },
{ { L"Telugu" }, { L"te" } },
{ { L"Thai" }, { L"th" } },
{ { L"Turkish" }, { L"tr" } },
{ { L"Udmurt" }, { L"udm" } },
{ { L"Ukrainian" }, { L"uk" } },
{ { L"Urdu" }, { L"ur" } },
{ { L"Uzbek" }, { L"uz" } },
{ { L"Uzbek (Cyrillic)" }, { L"uzbcyr" } },
{ { L"Vietnamese" }, { L"vi" } },
{ { L"Welsh" }, { L"cy" } },
{ { L"Xhosa" }, { L"xh" } },
{ { L"Yakut" }, { L"sah" } },
{ { L"Yiddish" }, { L"yi" } },
{ { L"Zulu" }, { L"zu" } },
{ { L"?" }, { L"auto" } }
};
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
{
//Thnx @Dangetsu
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"api.browser.yandex.ru",
L"GET",
FormatString(L"/dictionary/translate?brandID=yandex&statLang=%s&targetLang=%s&url=http://vnr.aniclan.com/&locale=ru&text=%s", (tlp.translateFrom == L"?") ? codes.at(tlp.translateTo) : codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text)).c_str()
}) {
//In case of sentence translation
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"text"].String())) {
if (translation.value() != L"") return { true, translation.value() };}
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
//In case of single word translation
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"directions"][0][L"translations"][0][L"variants"][0][L"meanings"][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 (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
}

View File

@ -107,7 +107,7 @@ Please contact Artikash with any problems, feature requests, or questions relati
You can do so via the project homepage (issues section) or via email You can do so via the project homepage (issues section) or via email
Source code available under GPLv3 at project homepage Source code available under GPLv3 at project homepage
If you like this project, please tell everyone about it! It's time to put AGTH down :))"; If you like this project, please tell everyone about it! It's time to put AGTH down :))";
const wchar_t* CL_OPTIONS = LR"(usage: Textractor [-p{process ID|"process name"}]... const wchar_t* CL_OPTIONS = LR"(usage: Textractor [-c] [-p{process ID|"process name"}]...
example: Textractor -p4466 -p"My Game.exe" tries to inject processes with ID 4466 or with name My Game.exe)"; example: Textractor -p4466 -p"My Game.exe" tries to inject processes with ID 4466 or with name My Game.exe)";
const wchar_t* UPDATE_AVAILABLE = L"Update available: download it from https://github.com/Artikash/Textractor/releases"; const wchar_t* UPDATE_AVAILABLE = L"Update available: download it from https://github.com/Artikash/Textractor/releases";
const wchar_t* ALREADY_INJECTED = L"Textractor: already injected"; const wchar_t* ALREADY_INJECTED = L"Textractor: already injected";
@ -160,6 +160,7 @@ 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* MAX_SENTENCE_SIZE = u8"Max sentence size"; const char* MAX_SENTENCE_SIZE = u8"Max sentence size";
const char* DONT_TRANSLATE_IF_MATCH = u8"Don't translate if match full";
const char* TOPMOST = u8"Always on top"; const char* TOPMOST = u8"Always on top";
const char* DICTIONARY = u8"Dictionary"; const char* DICTIONARY = u8"Dictionary";
const char* DICTIONARY_INSTRUCTIONS = u8R"(This file is used only for the "Dictionary" feature of the Extra Window extension. const char* DICTIONARY_INSTRUCTIONS = u8R"(This file is used only for the "Dictionary" feature of the Extra Window extension.
@ -187,6 +188,7 @@ const char* CENTERED_TEXT = u8"Centered text";
const char* AUTO_RESIZE_WINDOW_HEIGHT = u8"Auto resize window height"; const char* AUTO_RESIZE_WINDOW_HEIGHT = u8"Auto resize window height";
const char* CLICK_THROUGH = u8"Click through\tAlt+X"; const char* CLICK_THROUGH = u8"Click through\tAlt+X";
const char* HIDE_MOUSEOVER = u8"Hide while mouse on top"; const char* HIDE_MOUSEOVER = u8"Hide while mouse on top";
const char* HIDE_TEXT = u8"Hide/Show text\tAlt+T";
const char* OPACITY = u8"Opacity"; const char* OPACITY = u8"Opacity";
const char* BG_COLOR = u8"Background color"; const char* BG_COLOR = u8"Background color";
const char* TEXT_COLOR = u8"Text color"; const char* TEXT_COLOR = u8"Text color";
@ -195,6 +197,9 @@ const char* OUTLINE_COLOR = u8"Outline color";
const char* OUTLINE_SIZE = u8"Outline size"; const char* OUTLINE_SIZE = u8"Outline size";
const char* OUTLINE_SIZE_INFO = u8"Size in pixels (recommended to stay below 20% of the font size)"; const char* OUTLINE_SIZE_INFO = u8"Size in pixels (recommended to stay below 20% of the font size)";
const char* FONT = u8"Font"; const char* FONT = u8"Font";
const char* TIMER_HIDE_TEXT = u8"Timer hide text";
const char* TEXT_TIMEOUT = u8"Timeout (msec, 0=disabled)";
const char* TEXT_TIMEOUT_ADD_PER_CHAR = u8"Additional timeout per char (msec)";
const char* LUA_INTRO = u8R"(--[[ const char* LUA_INTRO = u8R"(--[[
ProcessSentence is called each time Textractor receives a sentence of text. ProcessSentence is called each time Textractor receives a sentence of text.
@ -616,7 +621,7 @@ padding: длина добавочных данных перед строкой
Сделать это вы можете на домашней странице (секция issues) или через электронную почту Сделать это вы можете на домашней странице (секция issues) или через электронную почту
Исходный код доступен по лицензии GPLv3 на домашней странице проекта Исходный код доступен по лицензии GPLv3 на домашней странице проекта
Если эта программа вам понравилась, расскажите всем о ней :))"; Если эта программа вам понравилась, расскажите всем о ней :))";
CL_OPTIONS = LR"(использование: Textractor [-p{process ID|"process name"}]... CL_OPTIONS = LR"(использование: Textractor [-c] [-p{process ID|"process name"}]...
пример: Textractor -p4466 -p"My Game.exe" попробует присоединиться к процессу с ID 4466 или с именем My Game.exe)"; пример: Textractor -p4466 -p"My Game.exe" попробует присоединиться к процессу с ID 4466 или с именем My Game.exe)";
UPDATE_AVAILABLE = L"Доступно обновление: загрузите его на https://github.com/Artikash/Textractor/releases"; UPDATE_AVAILABLE = L"Доступно обновление: загрузите его на https://github.com/Artikash/Textractor/releases";
ALREADY_INJECTED = L"Textractor: уже присоединен"; ALREADY_INJECTED = L"Textractor: уже присоединен";
@ -875,7 +880,7 @@ Puoi farlo attraverso la pagina principale del progetto (sezione issues) o via e
Il codice sorgente è disponibile sotto il GPLv3 nella pagina principale Il codice sorgente è disponibile sotto il GPLv3 nella pagina principale
Al momento sono in cerca di un nuovo lavoro: contattatemi per email se conoscete qualcuno che ingaggia periti informatici statunitensi Al momento sono in cerca di un nuovo lavoro: contattatemi per email se conoscete qualcuno che ingaggia periti informatici statunitensi
Se ti piace questo progetto, parlane con tutti per favore :))"; Se ti piace questo progetto, parlane con tutti per favore :))";
CL_OPTIONS = LR"(utilizzo: Textractor [-p{process ID|"process name"}]... CL_OPTIONS = LR"(utilizzo: Textractor [-c] [-p{process ID|"process name"}]...
esempio: Textractor -p4466 -p"My Game.exe" sta tentando di inniettare i processi con l'ID 4466 o con il nome My Game.exe)"; esempio: Textractor -p4466 -p"My Game.exe" sta tentando di inniettare i processi con l'ID 4466 o con il nome My Game.exe)";
UPDATE_AVAILABLE = L"Aggiornamento disponibile: scaricala da https://github.com/Artikash/Textractor/releases"; UPDATE_AVAILABLE = L"Aggiornamento disponibile: scaricala da https://github.com/Artikash/Textractor/releases";
ALREADY_INJECTED = L"Textractor: già inniettato"; ALREADY_INJECTED = L"Textractor: già inniettato";
@ -917,6 +922,7 @@ esempio: Textractor -p4466 -p"My Game.exe" sta tentando di inniettare i processi
EXTRA_WINDOW_INFO = u8R"(Tasto destro per cambiare le impostazioni EXTRA_WINDOW_INFO = u8R"(Tasto destro per cambiare le impostazioni
Clicca e trascina i bordi della finestra per muoverla, oppure nell'angolo in basso a destra per ridimensionare)"; Clicca e trascina i bordi della finestra per muoverla, oppure nell'angolo in basso a destra per ridimensionare)";
MAX_SENTENCE_SIZE = u8"Dimensione massima sentenza"; MAX_SENTENCE_SIZE = u8"Dimensione massima sentenza";
DONT_TRANSLATE_IF_MATCH = u8"Non traduce se corrisponde completamente";
TOPMOST = u8"Sempre in primo piano"; TOPMOST = u8"Sempre in primo piano";
DICTIONARY = u8"Dizionario"; DICTIONARY = u8"Dizionario";
DICTIONARY_INSTRUCTIONS = u8R"(Questo file è utilizzato solo per la funzione "Dizionario" dell'estenzione Extra Window. DICTIONARY_INSTRUCTIONS = u8R"(Questo file è utilizzato solo per la funzione "Dizionario" dell'estenzione Extra Window.
@ -945,6 +951,7 @@ Funziona solo se questa estenzione è usata direttamente dopo un'estensione di t
AUTO_RESIZE_WINDOW_HEIGHT = u8"Auto resize altezza finestra"; AUTO_RESIZE_WINDOW_HEIGHT = u8"Auto resize altezza finestra";
CLICK_THROUGH = u8"Clicca attraverso\tAlt+X"; CLICK_THROUGH = u8"Clicca attraverso\tAlt+X";
HIDE_MOUSEOVER = u8"Nascondi testo mouseover"; HIDE_MOUSEOVER = u8"Nascondi testo mouseover";
HIDE_TEXT = u8"Nascondi/Mostra testo\tAlt+T";
OPACITY = u8"Opacità"; OPACITY = u8"Opacità";
BG_COLOR = u8"Colore dello sfondo"; BG_COLOR = u8"Colore dello sfondo";
TEXT_COLOR = u8"Colore del testo"; TEXT_COLOR = u8"Colore del testo";
@ -953,6 +960,9 @@ Funziona solo se questa estenzione è usata direttamente dopo un'estensione di t
OUTLINE_SIZE = u8"Dimensione del contorno"; OUTLINE_SIZE = u8"Dimensione del contorno";
OUTLINE_SIZE_INFO = u8"Dimensione in pixel (consigliato di rimanere sotto il 20% della dimensione del font)"; OUTLINE_SIZE_INFO = u8"Dimensione in pixel (consigliato di rimanere sotto il 20% della dimensione del font)";
FONT = u8"Font"; FONT = u8"Font";
TIMER_HIDE_TEXT = u8"Timer nascondi testo";
TEXT_TIMEOUT = u8"Timeout (msec, 0=disattivato)";
TEXT_TIMEOUT_ADD_PER_CHAR = u8"Timeout aggiuntivo per carattere (msec)";
LUA_INTRO = u8R"(--[[ LUA_INTRO = u8R"(--[[
ProcessSentence è chiamato ogni volta che Textractor riceva una sentenza di testo. ProcessSentence è chiamato ogni volta che Textractor riceva una sentenza di testo.
@ -1311,7 +1321,7 @@ Veuillez me contacter pour tout problème, demande de fonctionnalité ou questio
Vous pouvez le faire via la page d'accueil du projet (section problèmes) ou par e-mail Vous pouvez le faire via la page d'accueil du projet (section problèmes) ou par e-mail
Code source disponible sous GPLv3 sur la page d'accueil du projet Code source disponible sous GPLv3 sur la page d'accueil du projet
Si vous aimez ce projet, parlez-en à tout le monde :))"; Si vous aimez ce projet, parlez-en à tout le monde :))";
CL_OPTIONS = LR"(usage: Textractor [-p{process ID|"process name"}]... CL_OPTIONS = LR"(usage: Textractor [-c] [-p{process ID|"process name"}]...
example: Textractor -p4466 -p"My Game.exe" tries to inject processes with ID 4466 or with name My Game.exe)"; example: Textractor -p4466 -p"My Game.exe" tries to inject processes with ID 4466 or with name My Game.exe)";
UPDATE_AVAILABLE = L"Mise à jour disponible: téléchargez-la depuis https://github.com/Artikash/Textractor/releases"; UPDATE_AVAILABLE = L"Mise à jour disponible: téléchargez-la depuis https://github.com/Artikash/Textractor/releases";
ALREADY_INJECTED = L"Textractor: déjà injecté"; ALREADY_INJECTED = L"Textractor: déjà injecté";

View File

@ -7983,6 +7983,7 @@ bool InsertCotophaHook2()
return false; return false;
} }
bool InsertCotophaHook4() bool InsertCotophaHook4()
{ {
//by Blu3train //by Blu3train
@ -8022,10 +8023,29 @@ bool InsertCotophaHook4()
} }
return false; return false;
} }
bool InsertCotophaHook3() {
const BYTE bytes[] = { 0x8B,0x75,0xB8,0x8B,0xCE,0x50,0xC6,0x45,0xFC,0x01,0xE8 };
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
ConsoleOutput("vnreng:Cotopha3: Cotopha3 not found");
return false;
}
HookParam myhp = {};
myhp.address = addr;
myhp.type = USING_UNICODE | USING_STRING | NO_CONTEXT;
myhp.offset = pusha_eax_off - 4;
char nameForUser[HOOK_NAME_SIZE] = "Cotopha3_EWideString";
NewHook(myhp, nameForUser);
ConsoleOutput("Insert: Cotopha3_EWideString Hook BY:IOV");
return true;
}
bool InsertCotophaHook() bool InsertCotophaHook()
{ {
return InsertCotophaHook4() || InsertCotophaHook1() || InsertCotophaHook2(); return InsertCotophaHook4() || InsertCotophaHook1() || InsertCotophaHook3() || InsertCotophaHook2();
} }
// jichi 5/10/2014 // jichi 5/10/2014
@ -10980,12 +11000,63 @@ bool InsertWolf2Hook()
NewHook(hp, "WolfRPG2"); NewHook(hp, "WolfRPG2");
return true; return true;
} }
//example-game:妹!せいかつ~ファンタジー~ by:iov
bool InsertWolf3Hook()
{
const BYTE bytes[] = { 0xC7,0x45,0xFC,0x00,0x00,0x00,0x00,0x8B,0x45,0x94,0x83,0xE0,0x01 };
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
ConsoleOutput("vnreng:WolfRPG: pattern3 not found");
return false;
}
HookParam myhp = {};
myhp.address = addr+41;
myhp.type = USING_STRING | NO_CONTEXT;
myhp.offset = pusha_eax_off - 4;
myhp.type |= DATA_INDIRECT;
myhp.index = 4;
char nameForUser[HOOK_NAME_SIZE] = "WolfRPG_String_Copy";
NewHook(myhp, nameForUser);
ConsoleOutput("Insert: WolfRPG_String_Copy Hook");
return true;
}
bool InsertWolf4Hook() {
const BYTE bytes[] = {0xC6,0x45,0xFC,0x29,0x8B,0x8D,0xE0,0xEF,0xFF,0xFF,0xE8,XX4,0x50,0x8B,0x4D,0xE8,0x2B,0x4D,0xEC };
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
ConsoleOutput("vnreng:WolfRPG: pattern4 not found");
return false;
}
HookParam myhp = {};
myhp.address = addr + 16;
myhp.type = USING_STRING | NO_CONTEXT;
myhp.offset = pusha_eax_off - 4;
// myhp.type |= DATA_INDIRECT;
// myhp.index = 4;
char nameForUser[HOOK_NAME_SIZE] = "WolfRPG4";
NewHook(myhp, nameForUser);
ConsoleOutput("Insert: WolfRPG4 Hook");
return true;
}
} // WolfRPG namespace } // WolfRPG namespace
bool InsertWolfHook() bool InsertWolfHook()
{ {
return InsertOldWolfHook(), InsertWolf2Hook(); return InsertOldWolfHook(), InsertWolf2Hook(), InsertWolf3Hook(), InsertWolf4Hook();
} }
bool InsertIGSDynamicHook(LPVOID addr, DWORD frame, DWORD stack) bool InsertIGSDynamicHook(LPVOID addr, DWORD frame, DWORD stack)
@ -11420,6 +11491,24 @@ void SpecialHookWillPlusA(DWORD esp_base, HookParam *, BYTE index, DWORD *data,
bool InsertWillPlusAHook() bool InsertWillPlusAHook()
{ {
//by iov
const BYTE bytes2[] = { 0x8B,0x00,0xFF,0x76,0xFC,0x8B,0xCF,0x50 };
ULONG range2 = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr2 = MemDbg::findBytes(bytes2, sizeof(bytes2), processStartAddress, processStartAddress + range2);
if (addr2) {
HookParam myhp = {};
myhp.address = addr2 + 2;
myhp.type = USING_UNICODE | NO_CONTEXT | USING_STRING;
myhp.offset = pusha_eax_off - 4;//esp+4
char nameForUser[HOOK_NAME_SIZE] = "WillPlus3_memcpy";
NewHook(myhp, nameForUser);
ConsoleOutput("Insert: WillPlus3_memcpy Hook");
return true;
}
const BYTE bytes[] = { const BYTE bytes[] = {
0x81,0xec, 0x14,0x08,0x00,0x00 // 0042B5E0 81EC 14080000 SUB ESP,0x814 ; jichi: text in eax, name in eax - 1024, able to copy 0x81,0xec, 0x14,0x08,0x00,0x00 // 0042B5E0 81EC 14080000 SUB ESP,0x814 ; jichi: text in eax, name in eax - 1024, able to copy
}; };
@ -11552,7 +11641,7 @@ static bool InsertNewWillPlusHook()
found = true; found = true;
} }
/* /*
hook cmp esi,0x3000 hook cmp esi or ebx,0x3000
Sample games: Sample games:
https://vndb.org/r54549 https://vndb.org/r54549
https://vndb.org/v22705 https://vndb.org/v22705
@ -11560,17 +11649,25 @@ static bool InsertNewWillPlusHook()
https://vndb.org/v25719 https://vndb.org/v25719
https://vndb.org/v27227 https://vndb.org/v27227
https://vndb.org/v27385 https://vndb.org/v27385
https://vndb.org/v34544
https://vndb.org/v35279
https://vndb.org/r94284
*/ */
const BYTE pattern[] = const BYTE pattern[] =
{ {
0x81,0xfe,0x00,0x30,0x00,0x00 //81FE 00300000 cmp esi,0x3000 0x81,XX, 0x00,0x30,0x00,0x00 // 81FE or FB 00300000 cmp esi or ebx,0x3000
// je xx
// hook here
}; };
for (auto addr : Util::SearchMemory(pattern, sizeof(pattern), PAGE_EXECUTE, processStartAddress, processStopAddress)) for (auto addr : Util::SearchMemory(pattern, sizeof(pattern), PAGE_EXECUTE, processStartAddress, processStopAddress))
{ {
BYTE byte = *(BYTE*)(addr + 1);
if (byte != 0xfe && byte != 0xfb)
continue;
HookParam hp = {}; HookParam hp = {};
hp.address = addr; hp.address = addr + 8;
hp.type = USING_UNICODE; hp.type = USING_UNICODE;
hp.offset = pusha_esi_off - 4; hp.offset = byte == 0xfe ? pusha_esi_off - 4 : pusha_ebx_off - 4;
hp.length_offset = 1; hp.length_offset = 1;
NewHook(hp, "WillPlus3"); NewHook(hp, "WillPlus3");
found = true; found = true;
@ -19886,9 +19983,115 @@ bool InsertRenpyHook()
ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll"); ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll");
return ok; return ok;
} }
static uintptr_t(*mono_assembly_get_image)(uintptr_t) = NULL;
static char* (*mono_image_get_name)(uintptr_t) = NULL;
static uintptr_t(*mono_class_from_name)(uintptr_t, char*, char*) = NULL;
static uintptr_t(*mono_class_vtable)(uintptr_t, uintptr_t) = NULL;
static void* (*mono_vtable_get_static_field_data)(uintptr_t) = NULL;
static uintptr_t(*mono_class_get_method_from_name)(uintptr_t, char*, int) = NULL;
static uintptr_t(*mono_class_get_property_from_name)(uintptr_t, char*) = NULL;
static uintptr_t(*mono_property_get_set_method)(uintptr_t) = NULL;
static uint64_t* (*mono_compile_method)(uintptr_t) = NULL;
static MonoDomain* (*mono_get_root_domain)() = NULL;
static void (*mono_thread_attach)(MonoDomain*) = NULL;
int getV8StringLength(uintptr_t stack, uintptr_t data) {
int len = *(int*)(data - 4);
int checkLength = len > 0 && len < PIPE_BUFFER_SIZE ? len : 0;
for (size_t i = 0; i < checkLength; i++)
{
if (*(WORD*)(data + i * 2) == 0x0)
return 0;
}
return checkLength * 2;
}
void MonoCallBack(uintptr_t assembly, void* userData) {
uintptr_t mono_property = NULL;
uintptr_t image = mono_assembly_get_image(assembly);
// TMP_Text TextMeshProUGUI
auto mono_tmp_class = mono_class_from_name(image, "TMPro", "TMP_Text");
auto mono_ugui_class = mono_class_from_name(image, "UnityEngine.UI", "Text");
auto mono_ngui_class = mono_class_from_name(image, "", "UILabel");
if (!mono_tmp_class && !mono_ugui_class && !mono_ngui_class)
return;
if (mono_tmp_class) {
mono_property = mono_class_get_property_from_name(mono_tmp_class, "text");
}
else if(mono_ugui_class)
{
mono_property = mono_class_get_property_from_name(mono_ugui_class, "text");
}
else if (mono_ngui_class) {
mono_property = mono_class_get_property_from_name(mono_ngui_class, "text");
}
if (mono_property == NULL)
return;
auto mono_set_method = mono_property_get_set_method(mono_property);
//注意必须调用mono_thread_attach 附加到主domain 才能调用 mono_method_get_unmanaged_thunk mono_compile_method 或mono_runtime_invoke
mono_thread_attach(mono_get_root_domain());
uint64_t* method_pointer = mono_compile_method(mono_set_method);
if (method_pointer) {
HookParam hp = {};
hp.type = USING_STRING | USING_UNICODE |DATA_INDIRECT;
hp.address = (uint64_t)method_pointer;
hp.offset = pusha_esp_off-4; // esp+8
hp.index = 12;
hp.padding = 12;
if (mono_tmp_class) {
ConsoleOutput("Mono_X86,Insert: TextMeshProUGUI_set_text Hook BY:IOV");
hp.length_fun = getV8StringLength;
NewHook(hp, "TextMeshProUGUI_set_text");
}
else if(mono_ugui_class)
{
ConsoleOutput("Mono_X86,Insert: UGUI_set_text Hook BY:IOV");
hp.length_fun = getV8StringLength;
NewHook(hp, "UGUI_set_text");
}
else if(mono_ngui_class)
{
ConsoleOutput("Mono_X86,Insert: NGUI_set_text Hook BY:IOV");
hp.length_fun = getV8StringLength;
NewHook(hp, "NGUI_set_text");
}
}
}
bool InsertMonoHooksByAssembly(HMODULE module) {
//void mono_assembly_foreach (GFunc func, gpointer user_data)
//遍历程序集。用于获取目标程序集的指针。其中的func 是一个回调函数要自己写。它有两个参数前者就是MonoAssembly*而后者则是user_data
static auto mono_assembly_foreach = (void (*)(void (*)(uintptr_t, void*), uintptr_t))GetProcAddress(module, "mono_assembly_foreach");
mono_assembly_get_image = (uintptr_t(*)(uintptr_t))GetProcAddress(module, "mono_assembly_get_image");
mono_image_get_name = (char* (*)(uintptr_t))GetProcAddress(module, "mono_image_get_name");
mono_class_from_name = (uintptr_t(*)(uintptr_t, char*, char*))GetProcAddress(module, "mono_class_from_name");
mono_class_get_property_from_name = (uintptr_t(*)(uintptr_t, char*))GetProcAddress(module, "mono_class_get_property_from_name");
mono_property_get_set_method = (uintptr_t(*)(uintptr_t))GetProcAddress(module, "mono_property_get_set_method");
mono_compile_method = (uint64_t * (*)(uintptr_t))GetProcAddress(module, "mono_compile_method");
mono_get_root_domain = (MonoDomain * (*)())GetProcAddress(module, "mono_get_root_domain");
mono_thread_attach = (void (*)(MonoDomain*))GetProcAddress(module, "mono_thread_attach");
if (mono_assembly_foreach && mono_assembly_get_image && mono_image_get_name && mono_class_from_name &&
mono_class_get_property_from_name && mono_property_get_set_method && mono_compile_method &&
mono_get_root_domain && mono_thread_attach) {
mono_assembly_foreach(MonoCallBack, NULL);
return true;
}
else
{
return false;
}
}
void InsertMonoHook(HMODULE h) void InsertMonoHook(HMODULE h)
{ {
static HMODULE mono = h; static HMODULE mono = h;
if (InsertMonoHooksByAssembly(mono))
return ;
/* Artikash 2/13/2019: /* Artikash 2/13/2019:
How to hook Mono/Unity3D: How to hook Mono/Unity3D:
Find all standard function prologs in memory with write/execute permission: these represent possible JIT compiled functions Find all standard function prologs in memory with write/execute permission: these represent possible JIT compiled functions
@ -19975,7 +20178,49 @@ bool NoAsciiFilter(LPVOID data, DWORD *size, HookParam *, BYTE)
return true; return true;
return false; return false;
} }
bool InsertAnimHook() {
const BYTE bytes[] = { 0xC7,0x45,0xFC,0x01,0x00,0x00,0x00,0x8B,0x4D,0x10,0x51,0x8D,0x8D,0x40,0x7E,0xFF,0xFF };
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
ConsoleOutput("vnreng:Anim: pattern not found");
return false;
}
HookParam myhp = {};
myhp.address = addr+10;
myhp.type = USING_STRING| NO_CONTEXT; // /HQ 不使用上下文区分 把所有线程的文本都提取
// data_offset
myhp.offset = pusha_ecx_off - 4;//esp+4
char nameForUser[HOOK_NAME_SIZE] = "Anim";
NewHook(myhp, nameForUser);
ConsoleOutput("Insert: Anim Hook by:IOV");
return true;
}
bool InsertAnim2Hook() {
const BYTE bytes[] = { 0xC7,0x45,0xFC,0x01,0x00,0x00,0x00,0x8B,0x45,0x10,0x50,0x8D,0x8D,0xAC,0x7E,0xFF,0xFF };
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
ConsoleOutput("vnreng:Anim2: pattern not found");
return false;
}
HookParam myhp = {};
myhp.address = addr + 10;
myhp.type = USING_STRING | NO_CONTEXT;
// data_offset
myhp.offset = pusha_eax_off - 4;//esp+4
char nameForUser[HOOK_NAME_SIZE] = "Anim2";
NewHook(myhp, nameForUser);
ConsoleOutput("Insert: Anim2 Hook by:IOV");
return true;
}
bool Anim3Filter(LPVOID data, DWORD *size, HookParam *, BYTE) bool Anim3Filter(LPVOID data, DWORD *size, HookParam *, BYTE)
{ {
auto text = reinterpret_cast<LPSTR>(data); auto text = reinterpret_cast<LPSTR>(data);

View File

@ -157,6 +157,9 @@ bool InsertWillPlusHook(); // WillPlus: Rio.arc
bool InsertWolfHook(); // Wolf: Data.wolf bool InsertWolfHook(); // Wolf: Data.wolf
bool InsertYukaSystemHooks(); // YukaSystem2: *.ykc bool InsertYukaSystemHooks(); // YukaSystem2: *.ykc
bool InsertYurisHook(); // YU-RIS: *.ypf bool InsertYurisHook(); // YU-RIS: *.ypf
bool InsertAnimHook(); //
bool InsertAnim2Hook(); //
bool InsertCotophaHook3();
bool InsertONScripterruHooks(); // ONScripter-RU: resource string bool InsertONScripterruHooks(); // ONScripter-RU: resource string
bool InsertSakanaGLHook(); // SakanaGL: sakanagl.dll bool InsertSakanaGLHook(); // SakanaGL: sakanagl.dll
bool InsertDebonosuWorksHook(); // DebonosuWorks: resource string bool InsertDebonosuWorksHook(); // DebonosuWorks: resource string

View File

@ -556,9 +556,10 @@ bool DetermineEngineByFile4()
} }
if (Util::CheckFile(L"voice\\*.pck")) { if (Util::CheckFile(L"voice\\*.pck")) {
return /*InsertAnimHook() || InsertAnim2Hook() ||*/ InsertAnim3Hook(); return InsertAnimHook() || InsertAnim2Hook() || InsertAnim3Hook();
} }
// jichi 11/22/2015: 凍京NECRO 体験版 // jichi 11/22/2015: 凍京NECRO 体験版
// Jazzinghen 23/05/2020: Add check for 凍京NECRO // Jazzinghen 23/05/2020: Add check for 凍京NECRO
// ResEdit shows multiple potential strings: // ResEdit shows multiple potential strings:

View File

@ -383,9 +383,132 @@ namespace Engine
} }
return found; return found;
} }
//MonoImage* mono_assembly_get_image(MonoAssembly* assembly)获取程序集的镜像。后面几乎所有的操作都会以MonoImage* 为第一个参数。
static uintptr_t (*mono_assembly_get_image)(uintptr_t) = NULL;
// const char* mono_image_get_name(MonoImage * image) :获取程序集名。我们用它判断哪个程序集是我们的目标
static char* (*mono_image_get_name)(uintptr_t) = NULL;
//MonoClass* mono_class_from_name (MonoImage *image, const char* name_space, const char *name):通过类名获取类(非实例)。
static uintptr_t(*mono_class_from_name)(uintptr_t, char*, char*) = NULL;
//MonoVTable* mono_class_vtable (MonoDomain *domain, MonoClass *klass)获取vtable我们通过它可以找到静态字段的起始地址。
static uintptr_t(*mono_class_vtable)(uintptr_t, uintptr_t) = NULL;
//void* mono_vtable_get_static_field_data (MonoVTable *vt):获取静态字段的起始地址。
static void* (*mono_vtable_get_static_field_data)(uintptr_t) = NULL;
//MonoMethod* mono_class_get_method_from_name (MonoClass *klass, const char *name, int param_count):获取方法(非native code地址)。
//其中param_count是参数数量可以输入-1来省略。此函数无法获取重载的方法但对于我们来说足够了。
static uintptr_t(*mono_class_get_method_from_name)(uintptr_t, char*,int) = NULL;
//获取属性。用它可以进一步获得属性的getter和setter。
//MonoProperty* mono_class_get_property_from_name(MonoClass* klass, const char* name)
static uintptr_t(*mono_class_get_property_from_name)(uintptr_t, char*) = NULL;
//获取属性的getter和setter。
//MonoMethod* mono_property_get_get_method(MonoProperty* prop) 与 MonoMethod* mono_property_get_set_method(MonoProperty* prop)
static uintptr_t(*mono_property_get_set_method)(uintptr_t) = NULL;
// 不安全返回方法的地址如果方法尚未编译则JIT开始编译。这个是解决问题的核心方法。 gpointer mono_compile_method (MonoMethod *method):
static uint64_t* (*mono_compile_method)(uintptr_t) = NULL;
//获取函数的非托管块指针 (native) gpointer mono_method_get_unmanaged_thunk (MonoMethod *method)
//使用这个来获取native代码 方法尚未编译,会执行编译 并提取 x86版本可能使用的是__stdcall
static uint64_t* (*mono_method_get_unmanaged_thunk)(uintptr_t) = NULL;
//MonoDomain* mono_get_root_domain (void) :获取主作用域。用于附加线程以及获取静态字段的地址。
static MonoDomain* (*mono_get_root_domain)() = NULL;
//void mono_thread_attach (MonoDomain*):附加到进程的主线程。这个操作是必须的。
static void (*mono_thread_attach)(MonoDomain*) = NULL;
//MonoAssembly* assembly而后者则是void* user_data
int getV8StringLength(uintptr_t stack, uintptr_t data) {
int len = *(int*)(data - 4);
int checkLength = len > 0 && len < PIPE_BUFFER_SIZE ? len : 0;
//检查是否为错误的unicode字符
for (size_t i = 0; i < checkLength; i++)
{
if (*(WORD*)(data + i * 2) == 0x0)
return 0;
}
return checkLength * 2;
}
void MonoCallBack(uintptr_t assembly, void* userData) {
uintptr_t mono_property = NULL;
uintptr_t image=mono_assembly_get_image(assembly);
// TMP_Text TextMeshProUGUI
auto mono_tmp_class=mono_class_from_name(image, "TMPro", "TMP_Text");
auto mono_ugui_class = mono_class_from_name(image, "UnityEngine.UI", "Text");
auto mono_ngui_class = mono_class_from_name(image, "", "UILabel");
if (!mono_tmp_class && !mono_ugui_class && !mono_ngui_class)
return;
if (mono_tmp_class) {
mono_property = mono_class_get_property_from_name(mono_tmp_class, "text");
}
else if (mono_ugui_class)
{
mono_property = mono_class_get_property_from_name(mono_ugui_class, "text");
}
else if (mono_ngui_class) {
mono_property = mono_class_get_property_from_name(mono_ngui_class, "text");
}
if (mono_property == NULL)
return;
auto mono_set_method= mono_property_get_set_method(mono_property);
//注意必须调用mono_thread_attach 附加到主domain 才能调用 mono_method_get_unmanaged_thunk mono_compile_method 或mono_runtime_invoke
mono_thread_attach(mono_get_root_domain());
uint64_t* method_pointer= mono_compile_method(mono_set_method);
if (method_pointer) {
HookParam hp = {};
hp.type = USING_STRING | USING_UNICODE;
hp.address = (uint64_t)method_pointer;
hp.offset = -0x28; // rdx
//hp.index = 0;
hp.padding = 0x14;
if (mono_tmp_class) {
ConsoleOutput("Mono_X64,Insert: TextMeshProUGUI_set_text Hook BY:IOV");
hp.length_fun = getV8StringLength;
NewHook(hp, "TextMeshProUGUI_set_text");
}
else if (mono_ugui_class)
{
ConsoleOutput("Mono_X64,Insert: UGUI_set_text Hook BY:IOV");
hp.length_fun = getV8StringLength;
NewHook(hp, "UGUI_set_text");
}
else if (mono_ngui_class)
{
ConsoleOutput("Mono_X64,Insert: NGUI_set_text Hook BY:IOV");
hp.length_fun = getV8StringLength;
NewHook(hp, "NGUI_set_text");
}
}
}
bool InsertMonoHooksByAssembly(HMODULE module) {
//void mono_assembly_foreach (GFunc func, gpointer user_data)
//遍历程序集。用于获取目标程序集的指针。其中的func 是一个回调函数要自己写。它有两个参数前者就是MonoAssembly*而后者则是user_data
static auto mono_assembly_foreach = (void (*)(void (*)(uintptr_t, void*), uintptr_t))GetProcAddress(module, "mono_assembly_foreach");
mono_assembly_get_image= (uintptr_t(*)(uintptr_t))GetProcAddress(module, "mono_assembly_get_image");
mono_image_get_name = (char* (*)(uintptr_t))GetProcAddress(module, "mono_image_get_name");
mono_class_from_name = (uintptr_t(*)(uintptr_t, char*, char*))GetProcAddress(module, "mono_class_from_name");
mono_class_get_property_from_name = (uintptr_t(*)(uintptr_t, char*))GetProcAddress(module, "mono_class_get_property_from_name");
mono_property_get_set_method = (uintptr_t(*)(uintptr_t))GetProcAddress(module, "mono_property_get_set_method");
mono_compile_method = (uint64_t * (*)(uintptr_t))GetProcAddress(module, "mono_compile_method");
//mono_method_get_unmanaged_thunk= (uint64_t * (*)(uintptr_t))GetProcAddress(module, "mono_method_get_unmanaged_thunk");
mono_get_root_domain = (MonoDomain * (*)())GetProcAddress(module, "mono_get_root_domain");
mono_thread_attach = (void (*)(MonoDomain*))GetProcAddress(module, "mono_thread_attach");
if (mono_assembly_foreach && mono_assembly_get_image && mono_image_get_name && mono_class_from_name &&
mono_class_get_property_from_name && mono_property_get_set_method && mono_compile_method &&
mono_get_root_domain && mono_thread_attach) {
mono_assembly_foreach(MonoCallBack, NULL);
return true;
}
else
{
return false;
}
}
bool InsertMonoHooks(HMODULE module) bool InsertMonoHooks(HMODULE module)
{ {
return InsertMonoHooksByAssembly(module);
auto SpecialHookMonoString = nullptr; auto SpecialHookMonoString = nullptr;
static HMODULE mono = module; static HMODULE mono = module;
bool ret = false; bool ret = false;
@ -567,7 +690,42 @@ namespace Engine
ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll"); ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll");
return false; return false;
} }
bool InsertGodotHook2_X64() { int getGodoStringLength(uintptr_t stack, uintptr_t data) {
int len = *(int*)(data - 4);
len--;
int checkLength = len > 0 && len < PIPE_BUFFER_SIZE ? len : 0;
//检查是否为错误的unicode字符
for (size_t i = 0; i < checkLength; i++)
{
if (*(WORD*)(data + i * 2) == 0x0)
return 0;
}
return checkLength * 2;
}
//BY:IOV
bool InsertGodotHook_X64() {
const BYTE bytes[] = { 0x8B,0x40,0xFC,0x83,0xF8,0x01,0x83,0xD0,0xFF,0x41,0x39,0xC6 };
ULONG64 range = min(processStopAddress - processStartAddress, X64_MAX_REL_ADDR);
for (auto addr : Util::SearchMemory(bytes, sizeof(bytes), PAGE_EXECUTE, processStartAddress, processStartAddress + range)) {
HookParam myhp = {};
myhp.address = addr;
myhp.type = USING_STRING | USING_UNICODE | NO_CONTEXT; // /HQ 不使用上下文区分 把所有线程的文本都提取
//myhp.padding = 0xc;//[esp+4]+padding
// data_offset
myhp.offset = -0xC-4;//RCX
myhp.length_fun = getGodoStringLength;
char nameForUser[HOOK_NAME_SIZE] = "RichTextLabel_add_text";
NewHook(myhp, nameForUser);
ConsoleOutput("Insert: Godot_add_text_X64 Hook ");
return true;
}
ConsoleOutput("vnreng:Godot_x64: pattern not found");
return false;
} bool InsertGodotHook2_X64() {
//by Blu3train //by Blu3train
/* /*
* Sample games: * Sample games: