Compare commits

..

1 Commits

Author SHA1 Message Date
Akash Mozumdar
a070a9e417 add basic extension 2021-03-21 18:45:27 -06:00
85 changed files with 1088 additions and 2216 deletions

View File

@ -5,21 +5,20 @@ skip_branch_with_pr: false
environment: environment:
matrix: matrix:
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
arch: x64 arch: x64
platform: x64 platform: x64
qtbin: msvc2017_64 qtbin: msvc2017_64
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019
arch: x86 arch: x86
platform: Win32 platform: Win32
qtbin: msvc2017 qtbin: msvc2017
before_build: before_build:
- git submodule update --init
- cd C:\ - cd C:\
- mkdir %arch% - mkdir %arch%
- cd %arch% - cd %arch%
- cmake -G "Visual Studio 15 2017" -A %platform% -DQt5_DIR="C:\Qt\5.13.2\%qtbin%\lib\cmake\Qt5" -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DVERSION="" ../Textractor - cmake -G "Visual Studio 16 2019" -A "%platform%" -DQt5_DIR="C:\Qt\5.13\%qtbin%\lib\cmake\Qt5" -DCMAKE_BUILD_TYPE="RelWithDebInfo" -DVERSION="" ../Textractor
build: build:
project: C:\%arch%\Textractor.sln project: C:\%arch%\Textractor.sln

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "texthook/minhook"]
path = texthook/minhook
url = https://github.com/TsudaKageyu/minhook.git

View File

@ -1,11 +1,6 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
project(Textractor) project(Textractor)
if (NOT MSVC)
message(FATAL_ERROR "Textractor can only be built with Visual Studio")
endif()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
add_compile_options( add_compile_options(
@ -18,11 +13,11 @@ add_compile_options(
) )
if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) if(${CMAKE_SIZEOF_VOID_P} EQUAL 8)
set(CMAKE_FINAL_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/builds/${CMAKE_BUILD_TYPE}_x64) set(CMAKE_FINAL_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/builds/${CMAKE_BUILD_TYPE}_x64)
link_directories(x64libs) link_directories(x64libs)
else() else()
set(CMAKE_FINAL_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/builds/${CMAKE_BUILD_TYPE}_x86) set(CMAKE_FINAL_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/builds/${CMAKE_BUILD_TYPE}_x86)
link_directories(x86libs) link_directories(x86libs)
endif() endif()
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY $<1:${CMAKE_FINAL_OUTPUT_DIRECTORY}>) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY $<1:${CMAKE_FINAL_OUTPUT_DIRECTORY}>)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY $<1:${CMAKE_FINAL_OUTPUT_DIRECTORY}>) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY $<1:${CMAKE_FINAL_OUTPUT_DIRECTORY}>)
@ -35,16 +30,15 @@ target_precompile_headers(pch PUBLIC include/common.h)
file(GLOB ASSETS assets/*) file(GLOB ASSETS assets/*)
file(COPY ${ASSETS} DESTINATION ${CMAKE_FINAL_OUTPUT_DIRECTORY}) file(COPY ${ASSETS} DESTINATION ${CMAKE_FINAL_OUTPUT_DIRECTORY})
include(QtUtils)
msvc_registry_search()
find_qt5(Core Widgets WinExtras WebSockets)
add_library(text text.cpp) add_library(text text.cpp)
target_compile_definitions(text PRIVATE ${TEXT_LANGUAGE}) target_compile_definitions(text PRIVATE ${TEXT_LANGUAGE})
link_libraries(text) link_libraries(text)
add_subdirectory(host)
add_subdirectory(texthook)
add_subdirectory(GUI) add_subdirectory(GUI)
add_subdirectory(texthook)
add_subdirectory(extensions) add_subdirectory(extensions)
add_subdirectory(test) add_subdirectory(test)
if (DEFINED VERSION)
add_subdirectory(GUI/host)
endif()
#add_subdirectory(GUI/host)

View File

@ -1,31 +1,40 @@
{ {
// See https://go.microsoft.com//fwlink//?linkid=834763 for more information about this file.
"configurations": [ "configurations": [
{ {
"name": "x86-Debug", "name": "x86-Debug",
"generator": "Ninja", "generator": "Ninja",
"configurationType": "Debug", "configurationType": "Debug",
"cmakeCommandArgs": "-DQT_ROOT=/Qt",
"inheritEnvironments": [ "msvc_x86" ] "inheritEnvironments": [ "msvc_x86" ]
}, },
{ {
"name": "x86-RelWithDebInfo", "name": "x86-RelWithDebInfo",
"generator": "Ninja", "generator": "Ninja",
"configurationType": "RelWithDebInfo", "configurationType": "RelWithDebInfo",
"cmakeCommandArgs": "-DQT_ROOT=/Qt", "inheritEnvironments": [ "msvc_x86" ]
},
{
"name": "x86-Release",
"generator": "Ninja",
"configurationType": "Release",
"inheritEnvironments": [ "msvc_x86" ] "inheritEnvironments": [ "msvc_x86" ]
}, },
{ {
"name": "x64-Debug", "name": "x64-Debug",
"generator": "Ninja", "generator": "Ninja",
"configurationType": "Debug", "configurationType": "Debug",
"cmakeCommandArgs": "-DQT_ROOT=/Qt",
"inheritEnvironments": [ "msvc_x64" ] "inheritEnvironments": [ "msvc_x64" ]
}, },
{ {
"name": "x64-RelWithDebInfo", "name": "x64-RelWithDebInfo",
"generator": "Ninja", "generator": "Ninja",
"configurationType": "RelWithDebInfo", "configurationType": "RelWithDebInfo",
"cmakeCommandArgs": "-DQT_ROOT=/Qt", "inheritEnvironments": [ "msvc_x64" ]
},
{
"name": "x64-Release",
"generator": "Ninja",
"configurationType": "Release",
"inheritEnvironments": [ "msvc_x64" ] "inheritEnvironments": [ "msvc_x64" ]
} }
] ]

View File

@ -1,32 +1,31 @@
# Developers # Developers
If you're on this list and want your link changed let me know.
- Textractor mainly made by [Artikash](https://github.com/Artikash) with the contributions of - Textractor mainly made by [Artikash](https://github.com/Artikash) with the contributions of
- Build system improvements by [DoumanAsh](https://github.com/DoumanAsh) - Build system improvements by [DoumanAsh](https://github.com/DoumanAsh)
- Original text removal in Extra Window by [Niakr1s](https://github.com/Niakr1s) - Original text removal in Extra Window by [Niakr1s](https://github.com/Niakr1s)
- CLI bugfixes by [tinyAdapter](https://github.com/tinyAdapter) - CLI bugfixes by [tinyAdapter](https://github.com/tinyAdapter)
- Hook codes for WAFFLE and WillPlus by [lgztx96](https://github.com/lgztx96) - Hook codes for WAFFLE and WillPlus by [lgztx96](https://github.com/lgztx96)
- Hook codes for TokyoNecro by [Jazzinghen](https://github.com/Jazzinghen) - Hook codes for TokyoNecro by [Jazzinghen](https://github.com/Jazzinghen)
- Hook codes for ShinyDays and Artemis by [luojunyuan](https://github.com/luojunyuan) - Hook codes for ShinyDays by [luojunyuan](https://github.com/luojunyuan)
- Google Chrome DevTools integration by [zeheyler](https://github.com/zeheyler) - Chrome DevTools integration by [zeheyler](https://github.com/zeheyler)
- AppVeyor setup by [silas1037](https://github.com/silas1037) - AppVeyor setup by [silas1037](https://github.com/silas1037)
- Improved attach dialog by [tera8m4](https://github.com/tera8m4) - French translation by Racky and [Gratusfr](https://github.com/Gratusfr)
- Regex Replacer, Papago, Systran, improvements to Extra Window, and bugfixes by [Blu3train](https://github.com/Blu3train)
- French translation by [Racky](mailto:maitrenoah@gmail.com) and [Gratusfr](https://github.com/Gratusfr)
- Spanish translation by [scese250](https://github.com/scese250) - Spanish translation by [scese250](https://github.com/scese250)
- Turkish translation by [niisokusu](https://reddit.com/u/niisokusu) - Turkish translation by [niisokusu](https://reddit.com/u/niisokusu)
- Simplified Chinese translation by [tinyAdapter](https://github.com/tinyAdapter), [lgztx96](https://github.com/lgztx96) and [chinanoahli](https://github.com/chinanoahli) - Simplified Chinese translation by [tinyAdapter](https://github.com/tinyAdapter) and [lgztx96](https://github.com/lgztx96)
- Russian translation by [TokcDK](https://github.com/TokcDK) - Russian translation by [TokcDK](https://github.com/TokcDK)
- Indonesian translation by [Hawxone](https://github.com/Hawxone) - Indonesian translation by [Hawxone](https://github.com/Hawxone)
- Portuguese translation by [TsumiHokiro](https://github.com/TsumiHokiro) - Portuguese translation by [TsumiHokiro](https://github.com/TsumiHokiro)
- Thai translation by [azmadoppler](https://github.com/azmadoppler) - Thai translation by [azmadoppler](https://github.com/azmadoppler)
- Korean translation by [O SK](mailto:afkl11@outlook.kr) - Korean translation by O SK
- Italian translation by [StarFang208](https://github.com/StarFang208) - Italian translation by [StarFang208](https://github.com/StarFang208)
- ITHVNR updated by [mireado](https://github.com/mireado), [Eguni](https://github.com/Eguni), and [IJEMIN](https://github.com/IJEMIN) - ITHVNR updated by [mireado](https://github.com/mireado), [Eguni](https://github.com/Eguni), and [IJEMIN](https://github.com/IJEMIN)
- ITHVNR originally made by [Stomp](mailto:zorkzero@hotmail.com) - ITHVNR originally made by [Stomp](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine)
- VNR engine made by [jichi](https://github.com/jichifly) - VNR engine made by [jichi](https://github.com/jichifly)
- ITH updated by [Andys](https://github.com/AndyScull) - ITH updated by [Andys](https://github.com/AndyScull)
- ITH originally made by [kaosu](https://code.google.com/archive/p/interactive-text-hooker) - ITH originally made by [kaosu](https://code.google.com/archive/p/interactive-text-hooker)
- Locale Emulator library made by [xupefei](https://github.com/xupefei)
If you're on this list and want your link changed let Artikash know. - MinHook library made by [TsudaKageyu](https://github.com/TsudaKageyu)
# THANK YOU!! # THANK YOU!!

View File

@ -1,14 +1,20 @@
include(QtUtils)
msvc_registry_search()
find_qt5(Core Widgets)
add_executable(Textractor WIN32 add_executable(Textractor WIN32
main.cpp main.cpp
exception.cpp
mainwindow.cpp mainwindow.cpp
extenwindow.cpp extenwindow.cpp
attachprocessdialog.cpp host/exception.cpp
host/host.cpp
host/textthread.cpp
host/hookcode.cpp
Textractor.rc Textractor.rc
Textractor.ico Textractor.ico
) )
target_precompile_headers(Textractor REUSE_FROM pch) target_precompile_headers(Textractor REUSE_FROM pch)
target_link_libraries(Textractor host Qt5::Widgets Qt5::WinExtras shell32 winhttp) target_link_libraries(Textractor Qt5::Widgets shell32 winhttp)
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Core.dll AND NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Cored.dll) if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Core.dll AND NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Cored.dll)
add_custom_command(TARGET Textractor add_custom_command(TARGET Textractor

View File

@ -1,39 +0,0 @@
#include "attachprocessdialog.h"
#include <QtWinExtras/QtWin>
extern const char* SELECT_PROCESS;
extern const char* ATTACH_INFO;
AttachProcessDialog::AttachProcessDialog(QWidget* parent, std::vector<std::pair<QString, HICON>> processIcons) :
QDialog(parent, Qt::WindowCloseButtonHint),
model(this)
{
ui.setupUi(this);
setWindowTitle(SELECT_PROCESS);
ui.label->setText(ATTACH_INFO);
ui.processList->setModel(&model);
QPixmap transparent(100, 100);
transparent.fill(QColor::fromRgba(0));
for (const auto& [process, icon] : processIcons)
{
auto item = new QStandardItem(icon ? QIcon(QtWin::fromHICON(icon)) : transparent, process);
item->setEditable(false);
model.appendRow(item);
}
connect(ui.buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(ui.processList, &QListView::clicked, [this](QModelIndex index) { ui.processEdit->setText(model.item(index.row())->text()); });
connect(ui.processList, &QListView::doubleClicked, this, &QDialog::accept);
connect(ui.processEdit, &QLineEdit::textEdited, [this](QString process)
{
for (int i = 0; i < model.rowCount(); ++i) ui.processList->setRowHidden(i, !model.item(i)->text().contains(process, Qt::CaseInsensitive));
});
connect(ui.processEdit, &QLineEdit::returnPressed, this, &QDialog::accept);
}
QString AttachProcessDialog::SelectedProcess()
{
return ui.processEdit->text();
}

View File

@ -1,17 +0,0 @@
#pragma once
#include "qtcommon.h"
#include "ui_attachprocessdialog.h"
#include <QStandardItemModel>
class AttachProcessDialog : public QDialog
{
public:
explicit AttachProcessDialog(QWidget* parent, std::vector<std::pair<QString, HICON>> processIcons);
QString SelectedProcess();
private:
Ui::AttachProcessDialog ui;
QStandardItemModel model;
QString selectedProcess;
};

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AttachProcessDialog</class>
<widget class="QWidget" name="AttachProcessDialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>400</height>
</rect>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="label"/>
</item>
<item>
<widget class="QLineEdit" name="processEdit"/>
</item>
<item>
<widget class="QListView" name="processList"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,5 +1,6 @@
#include "extenwindow.h" #include "extenwindow.h"
#include "ui_extenwindow.h" #include "ui_extenwindow.h"
#include <concrt.h>
#include <QMenu> #include <QMenu>
#include <QFileDialog> #include <QFileDialog>
#include <QDragEnterEvent> #include <QDragEnterEvent>
@ -18,7 +19,7 @@ extern const char* EXTEN_WINDOW_INSTRUCTIONS;
namespace namespace
{ {
constexpr auto EXTEN_SAVE_FILE = u8"SavedExtensions.txt"; constexpr auto EXTEN_SAVE_FILE = u8"SavedExtensions.txt";
constexpr auto DEFAULT_EXTENSIONS = u8"Remove Repeated Characters>Regex Filter>Copy to Clipboard>Google Translate>Extra Window>Extra Newlines"; constexpr auto DEFAULT_EXTENSIONS = u8"Remove Repeated Characters>Remove Repeated Phrases>Regex Filter>Copy to Clipboard>Bing Translate>Extra Window>Extra Newlines>Styler";
struct Extension struct Extension
{ {
@ -43,7 +44,7 @@ namespace
{ {
if (auto callback = (decltype(Extension::callback))GetProcAddress(module, "OnNewSentence")) if (auto callback = (decltype(Extension::callback))GetProcAddress(module, "OnNewSentence"))
{ {
std::scoped_lock lock(extenMutex); std::scoped_lock writeLock(extenMutex);
extensions.push_back({ S(extenName), callback }); extensions.push_back({ S(extenName), callback });
return true; return true;
} }
@ -55,14 +56,14 @@ namespace
void Unload(int index) void Unload(int index)
{ {
std::scoped_lock lock(extenMutex); std::scoped_lock writeLock(extenMutex);
FreeLibrary(GetModuleHandleW((extensions.at(index).name + L".xdll").c_str())); FreeLibrary(GetModuleHandleW((extensions.at(index).name + L".xdll").c_str()));
extensions.erase(extensions.begin() + index); extensions.erase(extensions.begin() + index);
} }
void Reorder(QStringList extenNames) void Reorder(QStringList extenNames)
{ {
std::scoped_lock lock(extenMutex); std::scoped_lock writeLock(extenMutex);
std::vector<Extension> extensions; std::vector<Extension> extensions;
for (auto extenName : extenNames) for (auto extenName : extenNames)
extensions.push_back(*std::find_if(::extensions.begin(), ::extensions.end(), [&](Extension extension) { return extension.name == S(extenName); })); extensions.push_back(*std::find_if(::extensions.begin(), ::extensions.end(), [&](Extension extension) { return extension.name == S(extenName); }));
@ -127,7 +128,7 @@ bool DispatchSentenceToExtensions(std::wstring& sentence, const InfoForExtension
void CleanupExtensions() void CleanupExtensions()
{ {
std::scoped_lock lock(extenMutex); std::scoped_lock writeLock(extenMutex);
for (auto extension : extensions) FreeLibrary(GetModuleHandleW((extension.name + L".xdll").c_str())); for (auto extension : extensions) FreeLibrary(GetModuleHandleW((extension.name + L".xdll").c_str()));
extensions.clear(); extensions.clear();
} }

View File

@ -2,6 +2,11 @@
#include "qtcommon.h" #include "qtcommon.h"
namespace Ui
{
class ExtenWindow;
}
struct InfoForExtension struct InfoForExtension
{ {
const char* name; const char* name;

View File

@ -1,4 +1,9 @@
# The CLI isn't used by Textractor itself, but is here for other people that want to build projects on top of Textractor # The CLI isn't used by Textractor itself, but is here for other people that want to build projects on top of Textractor
add_executable(TextractorCLI main.cpp) add_executable(TextractorCLI
cli.cpp
exception.cpp
host.cpp
textthread.cpp
hookcode.cpp
)
target_precompile_headers(TextractorCLI REUSE_FROM pch) target_precompile_headers(TextractorCLI REUSE_FROM pch)
target_link_libraries(TextractorCLI host)

View File

@ -1,5 +1,5 @@
#include "../host.h" #include "host.h"
#include "../hookcode.h" #include "hookcode.h"
#include <io.h> #include <io.h>
#include <fcntl.h> #include <fcntl.h>
#include <iostream> #include <iostream>

View File

@ -276,9 +276,7 @@ namespace HookCode
{ {
std::optional<HookParam> Parse(std::wstring code) std::optional<HookParam> Parse(std::wstring code)
{ {
if (code[0] == L'/') code.erase(0, 1); if (code[0] == L'/') code.erase(0, 1); // legacy/AGTH compatibility
code.erase(std::find(code.begin(), code.end(), L'/'), code.end()); // legacy/AGTH compatibility
Trim(code);
if (code[0] == L'R') return ParseRCode(code.erase(0, 1)); if (code[0] == L'R') return ParseRCode(code.erase(0, 1));
else if (code[0] == L'H') return ParseHCode(code.erase(0, 1)); else if (code[0] == L'H') return ParseHCode(code.erase(0, 1));
return {}; return {};
@ -294,7 +292,6 @@ namespace HookCode
assert(HexString(-12) == L"-C"), assert(HexString(-12) == L"-C"),
assert(HexString(12) == L"C"), assert(HexString(12) == L"C"),
assert(Parse(L"/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")), assert(Parse(L"/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")),
assert(Parse(L"/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA /KF")),
assert(Parse(L"HB4@0")), assert(Parse(L"HB4@0")),
assert(Parse(L"/RS65001#@44")), assert(Parse(L"/RS65001#@44")),
assert(Parse(L"HQ@4")), assert(Parse(L"HQ@4")),

View File

@ -63,7 +63,7 @@ void TextThread::Push(BYTE* data, int length)
else if (auto converted = StringToWideString(std::string((char*)data, length), hp.codepage ? hp.codepage : Host::defaultCodepage)) buffer.append(converted.value()); else if (auto converted = StringToWideString(std::string((char*)data, length), hp.codepage ? hp.codepage : Host::defaultCodepage)) buffer.append(converted.value());
else Host::AddConsoleOutput(INVALID_CODEPAGE); else Host::AddConsoleOutput(INVALID_CODEPAGE);
if (hp.type & FULL_STRING) buffer.push_back(L'\n'); if (hp.type & FULL_STRING) buffer.push_back(L'\n');
lastPushTime = GetTickCount64(); lastPushTime = GetTickCount();
if (filterRepetition) if (filterRepetition)
{ {
@ -87,7 +87,7 @@ void TextThread::Push(const wchar_t* data)
{ {
std::scoped_lock lock(bufferMutex); std::scoped_lock lock(bufferMutex);
// not sure if this should filter repetition // not sure if this should filter repetition
lastPushTime = GetTickCount64(); lastPushTime = GetTickCount();
buffer += data; buffer += data;
} }
@ -110,7 +110,7 @@ void TextThread::Flush()
std::scoped_lock lock(bufferMutex); std::scoped_lock lock(bufferMutex);
if (buffer.empty()) return; if (buffer.empty()) return;
if (buffer.size() > maxBufferSize || GetTickCount64() - lastPushTime > flushDelay) if (buffer.size() > maxBufferSize || GetTickCount() - lastPushTime > flushDelay)
{ {
AddSentence(std::move(buffer)); AddSentence(std::move(buffer));
buffer.clear(); buffer.clear();

View File

@ -8,9 +8,9 @@ public:
using OutputCallback = bool(*)(TextThread&, std::wstring&); using OutputCallback = bool(*)(TextThread&, std::wstring&);
inline static OutputCallback Output; inline static OutputCallback Output;
inline static bool filterRepetition = false; inline static bool filterRepetition = true;
inline static int flushDelay = 500; // flush every 500ms by default inline static int flushDelay = 400; // flush every 400ms by default
inline static int maxBufferSize = 3000; inline static int maxBufferSize = 1000;
inline static int maxHistorySize = 10'000'000; inline static int maxHistorySize = 10'000'000;
TextThread(ThreadParam tp, HookParam hp, std::optional<std::wstring> name = {}); TextThread(ThreadParam tp, HookParam hp, std::optional<std::wstring> name = {});
@ -36,7 +36,7 @@ private:
BYTE leadByte = 0; BYTE leadByte = 0;
std::unordered_set<wchar_t> repeatingChars; std::unordered_set<wchar_t> repeatingChars;
std::mutex bufferMutex; std::mutex bufferMutex;
DWORD64 lastPushTime = 0; DWORD lastPushTime = 0;
Synchronized<std::vector<std::wstring>> queuedSentences; Synchronized<std::vector<std::wstring>> queuedSentences;
struct TimerDeleter { void operator()(HANDLE h) { DeleteTimerQueueTimer(NULL, h, INVALID_HANDLE_VALUE); } }; struct TimerDeleter { void operator()(HANDLE h) { DeleteTimerQueueTimer(NULL, h, INVALID_HANDLE_VALUE); } };
AutoHandle<TimerDeleter> timer = NULL; AutoHandle<TimerDeleter> timer = NULL;

View File

@ -3,9 +3,8 @@
#include "defs.h" #include "defs.h"
#include "module.h" #include "module.h"
#include "extenwindow.h" #include "extenwindow.h"
#include "../host/host.h" #include "host/host.h"
#include "../host/hookcode.h" #include "host/hookcode.h"
#include "attachprocessdialog.h"
#include <shellapi.h> #include <shellapi.h>
#include <process.h> #include <process.h>
#include <QRegularExpression> #include <QRegularExpression>
@ -15,7 +14,6 @@
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QFileDialog> #include <QFileDialog>
#include <QFontDialog> #include <QFontDialog>
#include <QHash>
extern const char* ATTACH; extern const char* ATTACH;
extern const char* LAUNCH; extern const char* LAUNCH;
@ -30,6 +28,7 @@ extern const char* SETTINGS;
extern const char* EXTENSIONS; extern const char* EXTENSIONS;
extern const char* FONT; extern const char* FONT;
extern const char* SELECT_PROCESS; extern const char* SELECT_PROCESS;
extern const char* ATTACH_INFO;
extern const char* SELECT_PROCESS_INFO; extern const char* SELECT_PROCESS_INFO;
extern const char* FROM_COMPUTER; extern const char* FROM_COMPUTER;
extern const char* PROCESSES; extern const char* PROCESSES;
@ -88,8 +87,6 @@ namespace
TextThread* current = nullptr; TextThread* current = nullptr;
MainWindow* This = nullptr; MainWindow* This = nullptr;
void FindHooks();
QString TextThreadString(TextThread& thread) QString TextThreadString(TextThread& thread)
{ {
return QString("%1:%2:%3:%4:%5: %6").arg( return QString("%1:%2:%3:%4:%5: %6").arg(
@ -107,7 +104,7 @@ namespace
return { threadParam[1].toUInt(nullptr, 16), threadParam[2].toULongLong(nullptr, 16), threadParam[3].toULongLong(nullptr, 16), threadParam[4].toULongLong(nullptr, 16) }; return { threadParam[1].toUInt(nullptr, 16), threadParam[2].toULongLong(nullptr, 16), threadParam[3].toULongLong(nullptr, 16), threadParam[4].toULongLong(nullptr, 16) };
} }
std::array<InfoForExtension, 20> GetSentenceInfo(TextThread& thread) std::array<InfoForExtension, 10> GetSentenceInfo(TextThread& thread)
{ {
void (*AddText)(int64_t, const wchar_t*) = [](int64_t number, const wchar_t* text) void (*AddText)(int64_t, const wchar_t*) = [](int64_t number, const wchar_t* text)
{ {
@ -128,9 +125,6 @@ namespace
{ "hook address", (int64_t)thread.tp.addr }, { "hook address", (int64_t)thread.tp.addr },
{ "text handle", thread.handle }, { "text handle", thread.handle },
{ "text name", (int64_t)thread.name.c_str() }, { "text name", (int64_t)thread.name.c_str() },
{ "add sentence", (int64_t)AddSentence },
{ "add text", (int64_t)AddText },
{ "get selected process id", (int64_t)GetSelectedProcessId },
{ "void (*AddSentence)(int64_t number, const wchar_t* sentence)", (int64_t)AddSentence }, { "void (*AddSentence)(int64_t number, const wchar_t* sentence)", (int64_t)AddSentence },
{ "void (*AddText)(int64_t number, const wchar_t* text)", (int64_t)AddText }, { "void (*AddText)(int64_t number, const wchar_t* text)", (int64_t)AddText },
{ "DWORD (*GetSelectedProcessId)()", (int64_t)GetSelectedProcessId }, { "DWORD (*GetSelectedProcessId)()", (int64_t)GetSelectedProcessId },
@ -138,21 +132,6 @@ namespace
} }; } };
} }
void AttachSavedProcesses()
{
std::unordered_set<std::wstring> attachTargets;
if (autoAttach)
for (auto process : QString(QTextFile(GAME_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts))
attachTargets.insert(S(process));
if (autoAttachSavedOnly)
for (auto process : QString(QTextFile(HOOK_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts))
attachTargets.insert(S(process.split(" , ")[0]));
if (!attachTargets.empty())
for (auto [processId, processName] : GetAllProcesses())
if (processName && attachTargets.count(processName.value()) > 0 && alreadyAttached.count(processId) == 0) Host::InjectProcess(processId);
}
std::optional<std::wstring> UserSelectedProcess() std::optional<std::wstring> UserSelectedProcess()
{ {
QStringList savedProcesses = QString::fromUtf8(QTextFile(GAME_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts); QStringList savedProcesses = QString::fromUtf8(QTextFile(GAME_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts);
@ -160,7 +139,7 @@ namespace
savedProcesses.removeDuplicates(); savedProcesses.removeDuplicates();
savedProcesses.insert(1, FROM_COMPUTER); savedProcesses.insert(1, FROM_COMPUTER);
QString process = QInputDialog::getItem(This, SELECT_PROCESS, SELECT_PROCESS_INFO, savedProcesses, 0, true, &ok, Qt::WindowCloseButtonHint); QString process = QInputDialog::getItem(This, SELECT_PROCESS, SELECT_PROCESS_INFO, savedProcesses, 0, true, &ok, Qt::WindowCloseButtonHint);
if (process == FROM_COMPUTER) process = QDir::toNativeSeparators(QFileDialog::getOpenFileName(This, SELECT_PROCESS, "/", PROCESSES)); if (process == FROM_COMPUTER) process = QDir::toNativeSeparators(QFileDialog::getOpenFileName(This, SELECT_PROCESS, "C:\\", PROCESSES));
if (ok && process.contains('\\')) return S(process); if (ok && process.contains('\\')) return S(process);
return {}; return {};
} }
@ -174,31 +153,16 @@ namespace
void AttachProcess() void AttachProcess()
{ {
QMultiHash<QString, DWORD> processesMap; QMultiHash<QString, DWORD> allProcesses;
std::vector<std::pair<QString, HICON>> processIcons;
for (auto [processId, processName] : GetAllProcesses()) for (auto [processId, processName] : GetAllProcesses())
{
if (processName && (showSystemProcesses || processName->find(L":\\Windows\\") == std::string::npos)) if (processName && (showSystemProcesses || processName->find(L":\\Windows\\") == std::string::npos))
{ allProcesses.insert(QFileInfo(S(processName.value())).fileName(), processId);
QString fileName = QFileInfo(S(processName.value())).fileName();
if (!processesMap.contains(fileName))
{
HICON bigIcon, smallIcon;
ExtractIconExW(processName->c_str(), 0, &bigIcon, &smallIcon, 1);
processIcons.push_back({ fileName, bigIcon ? bigIcon : smallIcon });
}
processesMap.insert(fileName, processId);
}
}
std::sort(processIcons.begin(), processIcons.end(), [](auto one, auto two) { return QString::compare(one.first, two.first, Qt::CaseInsensitive) < 0; });
AttachProcessDialog attachProcessDialog(This, processIcons); QStringList processList(allProcesses.uniqueKeys());
if (attachProcessDialog.exec()) processList.sort(Qt::CaseInsensitive);
{ if (QString process = QInputDialog::getItem(This, SELECT_PROCESS, ATTACH_INFO, processList, 0, true, &ok, Qt::WindowCloseButtonHint); ok)
QString process = attachProcessDialog.SelectedProcess(); if (process.toInt(nullptr, 0)) Host::InjectProcess(process.toInt(nullptr, 0));
if (int processId = process.toInt(nullptr, 0)) Host::InjectProcess(processId); else for (auto processId : allProcesses.values(process)) Host::InjectProcess(processId);
else for (int processId : processesMap.values(process)) Host::InjectProcess(processId);
}
} }
void LaunchProcess() void LaunchProcess()
@ -276,8 +240,7 @@ namespace
void AddHook(QString hook) void AddHook(QString hook)
{ {
if (QString hookCode = QInputDialog::getText(This, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, hook, &ok, Qt::WindowCloseButtonHint); ok) if (QString hookCode = QInputDialog::getText(This, ADD_HOOK, CODE_INFODUMP, QLineEdit::Normal, hook, &ok, Qt::WindowCloseButtonHint); ok)
if (hookCode.startsWith("S") || hookCode.startsWith("/S")) FindHooks(); // backwards compatibility for old hook search UX if (auto hp = HookCode::Parse(S(hookCode))) try { Host::InsertHook(selectedProcessId, hp.value()); } catch (std::out_of_range) {}
else if (auto hp = HookCode::Parse(S(hookCode))) try { Host::InsertHook(selectedProcessId, hp.value()); } catch (std::out_of_range) {}
else Host::AddConsoleOutput(INVALID_CODE); else Host::AddConsoleOutput(INVALID_CODE);
} }
@ -687,7 +650,23 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
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);
std::thread([] { for (; ; Sleep(10000)) AttachSavedProcesses(); }).detach(); std::thread([]
{
for (; ; Sleep(10000))
{
std::unordered_set<std::wstring> attachTargets;
if (autoAttach)
for (auto process : QString(QTextFile(GAME_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts))
attachTargets.insert(S(process));
if (autoAttachSavedOnly)
for (auto process : QString(QTextFile(HOOK_SAVE_FILE, QIODevice::ReadOnly).readAll()).split("\n", QString::SkipEmptyParts))
attachTargets.insert(S(process.split(" , ")[0]));
if (!attachTargets.empty())
for (auto [processId, processName] : GetAllProcesses())
if (processName && attachTargets.count(processName.value()) > 0 && alreadyAttached.count(processId) == 0) Host::InjectProcess(processId);
}
}).detach();
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()

View File

@ -77,7 +77,7 @@
<widget class="QPlainTextEdit" name="textOutput"> <widget class="QPlainTextEdit" name="textOutput">
<property name="font"> <property name="font">
<font> <font>
<family>Arial Unicode MS</family> <family>Meiryo UI</family>
<pointsize>13</pointsize> <pointsize>13</pointsize>
</font> </font>
</property> </property>

Binary file not shown.

View File

@ -2,16 +2,17 @@
![How it looks](screenshot.png) ![How it looks](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
**Textractor** (a.k.a. NextHooker) is an open-source x86/x64 video game text hooker for Windows 7+ (and Wine) based off of [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br> **Textractor** (a.k.a. NextHooker) is an open-source x86/x64 video game text hooker for Windows/Wine based off of [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Watch the [tutorial video](docs/TUTORIAL.md) for a quick rundown on using it. Watch the [tutorial video](https://tinyurl.com/textractor-tutorial) for a quick rundown on using it.
## Download ## Download
Official stable releases of Textractor can be found [here](https://github.com/Artikash/Textractor/releases).<br> Official stable releases of Textractor can be found [here](https://github.com/Artikash/Textractor/releases).<br>
Experimental builds of Textractor from the latest source can be found [here](https://ci.appveyor.com/project/Artikash/textractor/history) (in the 'Artifacts' section of a job).<br>
The last release of ITHVNR can be found [here](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br> The last release of ITHVNR can be found [here](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
Experimental builds of Textractor (with debug info) from the latest source can be found [here](https://ci.appveyor.com/project/Artikash/textractor/history) in the 'Artifacts' section of each job. Try running vcredist if you get an error when starting Textractor.
## Features ## Features
@ -22,8 +23,8 @@ Experimental builds of Textractor (with debug info) from the latest source can b
## Support ## Support
Let me know of any bugs, games that Textractor has trouble hooking, feature requests, or other suggestions by posting an issue.<br> Please let me know of any bugs, games that Textractor has trouble hooking, feature requests, or other suggestions.<br>
If you have trouble hooking a game, please show me a way to freely download it or gift it to me on [Steam](https://steamcommunity.com/profiles/76561198097566313/). If you have trouble hooking a game please email me a place where I can freely download it, or gift it to me on [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## Extensions ## Extensions
@ -32,21 +33,21 @@ See the extensions folder for examples of what extensions can do.
## Contributing ## Contributing
All contributions are appreciated! Please email me at akashmozumdar@gmail.com if you have any questions about the codebase.<br> All contributions are appreciated! Please email (no, I'm not busy!) me at akashmozumdar@gmail.com if you have any questions about the codebase.<br>
You should use the standard process of making a pull request (fork, branch, commit changes, make PR from your branch to my master).<br> You should use the standard process of making a pull request (fork, branch, commit changes, make PR from your branch to my master).<br>
Contributing a translation is easy: [text.cpp](text.cpp) contains all of the text strings that you need to translate. Translations of this README or the tutorial video transcript are also welcome. Contributing a translation is easy: just translate the strings in text.cpp as well as this README and the subtitles of the tutorial video.
## Compiling ## Compiling
Before compiling Textractor, you need Qt version 5.13 and Visual Studio with CMake support.
Clone Textractor's source and initialize submodules with `git clone https://github.com/Artikash/Textractor.git` and `git submodule update --init`. Before compiling Textractor, you should get Qt version 5.13 and Visual Studio with CMake support. You should then be able to just open the source folder in Visual Studio and build.
You should then be able to just open the source folder in Visual Studio and build.
## Project Architecture ## Project Architecture
The host injects texthook into the target process and connects to it via 2 pipe files. The host (see GUI/host folder) injects texthook.dll (created from the texthook folder) into the target process and connects to it via 2 pipe files.<br>
Host writes to hostPipe, texthook writes to hookPipe.<br>
texthook waits for the pipe to be connected, then injects a few instructions into any text outputting functions (e.g. TextOut, GetGlyphOutline) that cause their input to be sent through the pipe.<br> texthook waits for the pipe to be connected, then injects a few instructions into any text outputting functions (e.g. TextOut, GetGlyphOutline) that cause their input to be sent through the pipe.<br>
Additional information about hooks is exchanged via shared memory.<br> Additional information about hooks is exchanged via shared memory.<br>
The text that the host receives through the pipe is then processed a little before being dispatched back to the GUI.<br> The text that the host receives through the pipe is then processed a little before being dispatched back to the GUI.<br>
Finally, the GUI dispatches the text to extensions before displaying it. Finally, the GUI dispatches the text to extensions before displaying it.
## [Developers](docs/CREDITS.md) ## [Developers](CREDITS.md)

View File

@ -1,52 +0,0 @@
# Textractor
![Wie es aussieht](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md)
**Textractor** (a.b.a. NextHooker) ist ein open-source x86/x64 Video spiel Text hooker für Windows 7+ (und Wine) basierend auf [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Schau das [Tutorial Video](docs/TUTORIAL.md) (auf Englisch) an für einen schnellen Überblick wie du Textractor verwendest.
## Download
Der offizielle Release ist [hier](https://github.com/Artikash/Textractor/releases) zu finden.<br>
Der letzte Release von ITHVNR ist [hier](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
Der experimentelle Release von Textractor (mit debug Informationen) ist [hier](https://ci.appveyor.com/project/Artikash/textractor/history) in der 'Artifacts'
Kategorie des jeweiligen Jobs.
## Features
- Modular und Anpassbar
- Automatischen 'hooken' von mehreren Engines (einige davon welche keine VNR Support haben!)
- Text 'hooken' mithilfe von /H "hook" Codes (die meisten AGTH codes funktionieren)
- Automatische suche nach funktionierenden Hook's
## Support
Wenn ihr irgendwelche Fehler, Spiele bei denen Textractor nicht funktioniert, oder Fragen/Anmerkungen habt lasst es mich bitte wissen.<br>
Falls ihr Probleme mit einem Spiel habt, schickt mir einen kostenlosen download Link von dem Spiel oder schenkt es mir auf [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## Erweiterungen
Siehe [Example Extension project](https://github.com/Artikash/ExampleExtension) für Anleitungen, wie man eine Erweiterung erstellt.<br>
Im 'Extensions' Ordner sind Beispiele für Erweiterungen.
## Unterstützen
Ich bin dankbar für alle Unterstützungen! Schickt mir eine E-Mail an akashmozumdar@gmail.com falls ihr Fragen zur Codebasis habt.<br>
Verwendet bitte als Standard eine pull Request (fork, branch, commit) zum Master Release.<br>
Zu Übersetzungen beizutragen ist einfach: [text.cpp](text.cpp) enthält alle text Strings welche übersetzt werden sollen. Übersetzungen der README oder des Tutorial Video Transkripts sind ebenfalls willkommen.
## Compiling
Zum Compilen braucht ihr Qt Version 5.13 und Visual Studio mit CMake Unterstützung.
Erstellt einen Clone vom Quellcode und initialisiert die submodule mit 'git clone https://github.com/Artikash/Textractor.git' und 'git submodule update --init'.
Ihr solltet danach in der Lage sein, den Quellordner in Visual Studio zu öffnen und anzufangen.
## Projekt Architektur
Der Host injiziert texthooks in den ziel Prozess und verbindet ihn mit 2 Pipe Dateien.
Texthook wartet auf die Pipe und injiziert Instruktionen für den Text Output (z.b. TextOut, GetGlyphOutline) welche durch die Pipe gesendet werden.<br>
Weitere Informationen werden durch geteilten Speicher ausgetauscht.<br>
Der Text, welchen der Host durch die Pipe erhält, wird dann verarbeitet, bevor er wieder an die GUI gesendet wird.<br>
Zu guter Letzt, sendet die GUI den Text an die Erweiterung, welche einen lesbaren Output anzeigt.
## [Entwickler](docs/CREDITS.md)

View File

@ -1,8 +1,8 @@
# Textractor # Textractor
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
## [Video tutorial](docs/TUTORIAL.md) ## [Video tutorial](https://tinyurl.com/textractor-tutorial)
## Descripción general ## Descripción general

View File

@ -2,15 +2,16 @@
![À quoi ça ressemble](screenshot.png) ![À quoi ça ressemble](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
**Textractor** (a.k.a. NextHooker) est un traducteur de jeux-videos basé surtout sur du texte en open source x86/x64 pour Windows/Wine [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br> **Textractor** (a.k.a. NextHooker) est un traducteur de jeux-videos basé surtout sur du texte en open source x86/x64 pour Windows/Wine [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Regarde le [tutorial video](docs/TUTORIAL.md) pour un aperçu rapide de son utilisation. Regarde le [tutorial video](https://tinyurl.com/textractor-tutorial) pour un aperçu rapide de son utilisation.
## Téléchargement ## Téléchargement
Les versions de Textractor peuvent être trouvées[here](https://github.com/Artikash/Textractor/releases).<br> Les versions de Textractor peuvent être trouvées[here](https://github.com/Artikash/Textractor/releases).<br>
Le denière version de THVNR peut etre trouvé [here](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO). Le denière version de THVNR peut etre trouvé [here](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
Essayez de démarrer Vcredist si vous avez une erreur en lançant Textractor.
## Nouveautés ## Nouveautés
- Hautement extensible et personnalisable - Hautement extensible et personnalisable
@ -30,9 +31,9 @@ Voir le dossier des extensions pour des exemples de ce que les extensions peuven
## Contribution ## Contribution
Toutes les contributions sont appréciées! Veuillez m'envoyer un e-mail À akashmozumdar@gmail.com si vous avez des questions sur la base de code. <br> Toutes les contributions sont appréciées! Veuillez m'envoyer un e-mail (non, je ne suis pas occupé!) À akashmozumdar@gmail.com si vous avez des questions sur la base de code. <br>
Vous devez utiliser le processus standard de création d'une demande d'extraction (fork, branch, commit changes, make PR from your branch to my master). <br> Vous devez utiliser le processus standard de création d'une demande d'extraction (fork, branch, commit changes, make PR from your branch to my master). <br>
Contribuer à une traduction est simple: il suffit de traduire les chaînes dans [text.cpp](text.cpp) ainsi que ce fichier README. Contribuer à une traduction est simple: il suffit de traduire les chaînes dans text.cpp ainsi que ce fichier README.
## Compilation ## Compilation
@ -48,4 +49,4 @@ Des informations supplémentaires sur les hooks sont échangées via la mémoire
Le texte que l'hôte reçoit via le canal est ensuite traité un peu avant d'être renvoyé à l'interface graphique. <br> Le texte que l'hôte reçoit via le canal est ensuite traité un peu avant d'être renvoyé à l'interface graphique. <br>
Enfin, l'interface graphique envoie le texte aux extensions avant de l'afficher. Enfin, l'interface graphique envoie le texte aux extensions avant de l'afficher.
## [Développeurs](docs/CREDITS.md) ## [Développeurs](CREDITS.md)

View File

@ -2,15 +2,16 @@
![How it looks](screenshot.png) ![How it looks](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
**Textractor** (a.k.a NextHooker) adalah teks hooker video game untuk Windows/Wine x86/x64 berbasis open-source yang didasari oleh [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br> **Textractor** (a.k.a NextHooker) adalah text hooker video game untuk Windows/Wine x86/x64 berbasis open-source yang didasari oleh [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Lihat [video tutorial](docs/TUTORIAL.md) untuk mengetahui bagaimana cara menggunakannya. Lihat [video tutorial](https://tinyurl.com/textractor-tutorial) untuk mengetahui bagaimana cara menggunakannya.
## Pengunduhan ## Pengunduhan
Rilisan Textractor dapat diunduh [disini](https://github.com/Artikash/Textractor/releases).<br> Rilisan Textractor dapat diunduh [disini](https://github.com/Artikash/Textractor/releases).<br>
Rilisan Terakhir ITHVNR dapat diunduh [disini](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO). Rilisan Terakhir ITHVNR dapat diunduh [disini](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
Coba jalankan vcredist.x86.exe jika kamu memiliki masalah ketika menjalankan Textractor.
## Fitur ## Fitur
@ -21,33 +22,36 @@ Rilisan Terakhir ITHVNR dapat diunduh [disini](https://drive.google.com/open?id=
## Dukungan ## Dukungan
Tolong beritahu saya jika kamu menemukan kutu, game yang tidak dapat di tempel oleh Textractor, permintaan fitur, atau usulan lain.<br> Please let me know of any bugs, games that Textractor has trouble hooking, feature requests, or other suggestions.<br>
Jika kamu memiliki masalah dalam menempelkan kedalam game tolong email saya link agar saya dapat mengunduh game tersebut, atau hadiahkan game tersebut di [Steam](https://steamcommunity.com/profiles/76561198097566313/). If you have trouble hooking a game please email me a place where I can freely download it, or gift it to me on [Steam](https://steamcommunity.com/profiles/76561198097566313/).
Tolong beritahu saya jika kamu menemukan bug, game yang tidak dapat di tempel oleh Textractor, permintaan fitur, atau usulan lain.<br>
Jika kamu memiliki masalah dalam menempelkan kedalam game tolong email saya link agar saya dapat mendownload game tersebut, atau gift game tersebut di [Steam](https://steamcommunity.com/profiles/76561198097566313/).
## Ekstensi ## Ekstensi
Lihat [project sampel ekstensi saya](https://github.com/Artikash/ExampleExtension) untuk melihat bagaimana cara membuat ekstensi.<br> Lihat [project sampel ekstensi saya](https://github.com/Artikash/ExampleExtension) untuk melihat bagaimana cara membuat ekstensi.<br>
Lihat ekstensi folder untuk melihat sampel ekstensi. Lihat folder extensions untuk melihat sampel ekstensi.
## Kontribusi ## Kontribusi
Seluruh kontribusi diapresiasi! Tolong email saya di akashmozumdar@gmail.com jika kamu memiliki pertanyaan mengenai kode dasar nya.<br> Seluruh kontribusi diapresiasi! Tolong email (tidak, saya tidak sibuk!) saya di akashmozumdar@gmail.com jika kamu memiliki pertanyaan mengenai codebase nya.<br>
Kamu harus menggunakan proses standar dalam membuat permintaan pull(fork, cabang, perubahan commit, membuat PR dari cabang kamu ke master saya).<br> Kamu harus menggunakan proses standar dalam membuat permintaan pull(fork, branch, commit changes, membuat PR dari branch kamu ke master saya).<br>
Berkontribusi dalam penerjemahan dapat dilakukan dengan mudah : cukup terjemahkan string dari [text.cpp](text.cpp) lalu terjemahkan README ini. Berkontribusi dalam penerjemahan dapat dilakukan dengan mudah : cukup terjemahkan string dari text.cpp lalu terjemahkan README ini.
## Mengcompile ## Compiling
Sebelum melakukan proses compile *Textractor*, kamu harus memiliki Visual Studio dengan dukungan Cmake, juga dengan Qt version 5.13<br> Sebelum melakukan proses compile *Textractor*, kamu harus memiliki Visual Studio dengan dukungan Cmake, juga dengan Qt version 5.13<br>
Lalu kamu dapat membuka folder di Visual Studio, dan build. Jalankan Textractor.exe. Lalu kamu dapat membuka folder di Visual Studio, dan build. Run Textractor.exe.
## Arsitektur Project ## Arsitektur Project
Host (lihat folder host) menyuntikan texthook.dll (dibuat dari folder texthook) kedalam target proses dan disambungkan lewat 2 file pipe.<br> Host (lihat folder GUI/host) menginject texthook.dll (dibuat dari folder texthook) kedalam target process dan disambungkan lewat 2 file pipe.<br>
Host menulis ke hostPipe, texthook menulis ke hookPipe.<br> Host menulis ke hostPipe, texthook menulis ke hookPipe.<br>
texthook menunggu pipe tersambung, lalu menyuntikan beberapa instruksi ke teks yang menghasilkan fungsi (contoh: TextOut, GetGlyphOutline) yang membuat input dikirim melewati pipa.<br> texthook menunggu pipe tersambung, lalu menginject beberapa instruksi ke teks yang menghasilkan fungsi (contoh: TextOut, GetGlyphOutline) yang membuat input dikirim melewati pipa.<br>
Informasi tambahan tentang hook dipindahkan melewati shared memory.<br> Informasi tambahan tentang hook dipindahkan melewati shared memory.<br>
Teks yang diterima host melewati pipe lalu diproses lagi sebelum dikembalikan ke GUI.<br> Text yang diterima host melewati pipe lalu diproses lagi sebelum dikembalikan ke GUI.<br>
Dan pada akhirnya, GUI melepas teks ke ekstensi sebelum menampilkan teks. Dan pada akhirnya, GUI melepas text ke ekstensi sebelum menampilkan teks.
## [Pengembang](docs/CREDITS.md) ## [Pengembang](CREDITS.md)

View File

@ -2,15 +2,16 @@
![Come si vede](screenshot.png) ![Come si vede](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
**Textractor** (a.k.a. NextHooker) è un agganciatore di testi di videogiochi open-source per Windows/Wine basato su[ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br> **Textractor** (a.k.a. NextHooker) è un agganciatore di testi di videogiochi open-source per Windows/Wine basato su[ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Guarda il [video tutorial](docs/TUTORIAL.md) per una sintesi veloce sul suo utilizzo. Guarda il [video tutorial](https://tinyurl.com/textractor-tutorial) per una sintesi veloce sul suo utilizzo.
## Scarica ## Scarica
Le uscite di Textractor possono essere trovate [qui](https://github.com/Artikash/Textractor/releases).<br> Le uscite di Textractor possono essere trovate [qui](https://github.com/Artikash/Textractor/releases).<br>
L'ultima uscita di ITHVNR può essere trovata [qui](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO). L'ultima uscita di ITHVNR può essere trovata [qui](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
Prova ad avviare vcredist se ti esce un errore quando avvi Textractor.
## Caratteristiche ## Caratteristiche
@ -33,7 +34,7 @@ Guardate la cartella delle estenzioni per esempi di cosa possono fare le estenzi
Tutti i contributi sono apprezzati! Inviatemi un email a akashmozumdar@gmail.com se avete delle domande sul codebase.<br> Tutti i contributi sono apprezzati! Inviatemi un email a akashmozumdar@gmail.com se avete delle domande sul codebase.<br>
Dovreste usare il processo standard di creare una pull request (fork, branch, commit changes, crea PR dal vostro ramo al mio master).<br> Dovreste usare il processo standard di creare una pull request (fork, branch, commit changes, crea PR dal vostro ramo al mio master).<br>
Contribuire alla traduzione è semplice: traduci le stringhe in [text.cpp](text.cpp) cosi come questo README. Contribuire alla traduzione è semplice: traduci le stringhe in text.cpp cosi come questo README.
## Compiling ## Compiling
@ -42,11 +43,11 @@ Dovresti essere in grado di aprire la cartella in Visual Studio, e costruire. Av
## Architettura del progetto ## Architettura del progetto
L'host (guarda la cartella host) innietta texthook.dll (creato dalla cartella texthook) nel processo e lo connette attraverso due file pipe.<br> L'host (guarda la cartella GUI/host) innietta texthook.dll (creato dalla cartella texthook) nel processo e lo connette attraverso due file pipe.<br>
L'host scrive a hostPipe, texthook scrive a hookPipe.<br> L'host scrive a hostPipe, texthook scrive a hookPipe.<br>
Texthook aspetta per il pipe di essere connesso, poi innietta alcune istruzione in qualunque funzione di immissione del testo (es. TextOut, GetGlyphOutline) che causa il loro input di essere inviato attraverso il pipe.<br> Texthook aspetta per il pipe di essere connesso, poi innietta alcune istruzione in qualunque funzione di immissione del testo (es. TextOut, GetGlyphOutline) che causa il loro input di essere inviato attraverso il pipe.<br>
Informazioni aggiuntive sui ganci soo scambiati attraverso la memorio condivisa.<br> Informazioni aggiuntive sui ganci soo scambiati attraverso la memorio condivisa.<br>
Il testo che l'host riceve attraverso il pipe è poi processato un poco prima di essere rinviato alla GUI.<br> Il testo che l'host riceve attraverso il pipe è poi processato un poco prima di essere rinviato alla GUI.<br>
Infine, la GUI dispone il testo alle estenzioni prima di mostrarle. Infine, la GUI dispone il testo alle estenzioni prima di mostrarle.
## [Sviluppatori](docs/CREDITS.md) ## [Sviluppatori](CREDITS.md)

View File

@ -1,6 +1,6 @@
# Textractor # Textractor
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
**Textractor** はビジュアルノベル文字抽出プログラム。 **Textractor** はビジュアルノベル文字抽出プログラム。

View File

@ -2,15 +2,16 @@
![How it looks](screenshot.png) ![How it looks](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
**Textractor** (a.k.a. NextHooker)는 Windows/Wine에서 작동하는 [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine)을 기반으로 한 오픈소스 x86/x64 비디오게임 텍스트 후커 입니다.<br> **Textractor** (a.k.a. NextHooker)는 Windows/Wine에서 작동하는 [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine)을 기반으로 한 오픈소스 x86/x64 비디오게임 텍스트 후커 입니다.<br>
빠른 사용법의 이해를 위해 [tutorial video](docs/TUTORIAL.md) 를 참고하세요. 빠른 사용법의 이해를 위해 [tutorial video](https://tinyurl.com/textractor-tutorial) 를 참고하세요.
## 다운로드 ## 다운로드
[여기](https://github.com/Artikash/Textractor/releases)에서 Textractor 최신버전을 받으실 수 있습니다.<br> [여기](https://github.com/Artikash/Textractor/releases)에서 Textractor 최신버전을 받으실 수 있습니다.<br>
최신버전의 ITHVNR은 [여기](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO)서 받을 수 있습니다. 최신버전의 ITHVNR은 [여기](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO)서 받을 수 있습니다.<br>
Textractor 실행오류를 겪는다면 vcredist를 실행해 보시기 바랍니다.
## 특징 ## 특징
@ -40,11 +41,11 @@
## 프로젝트 아키텍쳐 ## 프로젝트 아키텍쳐
The host (see host folder) injects texthook.dll (created from the texthook folder) into the target process and connects to it via 2 pipe files.<br> The host (see GUI/host folder) injects texthook.dll (created from the texthook folder) into the target process and connects to it via 2 pipe files.<br>
Host writes to hostPipe, texthook writes to hookPipe.<br> Host writes to hostPipe, texthook writes to hookPipe.<br>
texthook waits for the pipe to be connected, then injects a few instructions into any text outputting functions (e.g. TextOut, GetGlyphOutline) that cause their input to be sent through the pipe.<br> texthook waits for the pipe to be connected, then injects a few instructions into any text outputting functions (e.g. TextOut, GetGlyphOutline) that cause their input to be sent through the pipe.<br>
Additional information about hooks is exchanged via shared memory.<br> Additional information about hooks is exchanged via shared memory.<br>
The text that the host receives through the pipe is then processed a little before being dispatched back to the GUI.<br> The text that the host receives through the pipe is then processed a little before being dispatched back to the GUI.<br>
Finally, the GUI dispatches the text to extensions before displaying it. Finally, the GUI dispatches the text to extensions before displaying it.
## [개발자들](docs/CREDITS.md) ## [개발자들](CREDITS.md)

View File

@ -2,15 +2,16 @@
![Como se Parece](screenshot.png) ![Como se Parece](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
**Textractor** (também conhecido como NextHooker) é um extrator de textos de video-games x86/x64 para Windows/Wine baseado no [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br> **Textractor** (também conhecido como NextHooker) é um extrator de textos de video-games x86/x64 para Windows/Wine baseado no [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Assista ao [vídeo tutorial](docs/TUTORIAL.md) para uma rápida apresentação de como utilizá-lo. Assista ao [vídeo tutorial](https://tinyurl.com/textractor-tutorial) para uma rápida apresentação de como utilizá-lo.
## Download ## Download
As versões lançadas podem ser encontradas [aqui](https://github.com/Artikash/Textractor/releases).<br> As versões lançadas podem ser encontradas [aqui](https://github.com/Artikash/Textractor/releases).<br>
A última versão lançada do ITHVNR pode ser encontrada [aqui](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO). A última versão lançada do ITHVNR pode ser encontrada [aqui](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
Tente rodar o vcredist se você encontrar algum erro ao iniciar o Textractor.
## Recursos e Funções ## Recursos e Funções
@ -31,9 +32,9 @@ Veja a pasta de extensões para mais exemplos do que as extensões são capazes
## Contribuindo ## Contribuindo
Todas contribuições são bem-vindas! Por favor, me mande um e-mail no endereço akashmozumdar@gmail.com caso tenha alguma dúvida quanto ao codebase.<br> Todas contribuições são bem-vindas! Por favor, me mande um e-mail (não, não sou ocupado!) no endereço akashmozumdar@gmail.com caso tenha alguma dúvida quanto ao codebase.<br>
Você deve seguir o processo padrão de fazer um pull request (fork, branch, realizar mudanças, realizar o PR do seu branch para o meu master).<br> Você deve seguir o processo padrão de fazer um pull request (fork, branch, realizar mudanças, realizar o PR do seu branch para o meu master).<br>
Contribuir com uma tradução é fácil: basta traduzir as linhas do [text.cpp](text.cpp) assim como esse README. Contribuir com uma tradução é fácil: basta traduzir as linhas do text.cpp assim como esse README.
## Compilando ## Compilando
@ -42,11 +43,11 @@ Você deverá então ser capaz de simplesmente abrir uma pasta no Visual Studio
## Arquitetura do Projeto ## Arquitetura do Projeto
O host (veja a pasta host) injeta o texthook.dll (criado a partir da pasta texthook) dentro do processo-alvo e se conecta a ele por meio de 2 arquivos pipe.<br> O host (veja a pasta GUI/host) injeta o texthook.dll (criado a partir da pasta texthook) dentro do processo-alvo e se conecta a ele por meio de 2 arquivos pipe.<br>
O Host escreve para hostPipe, o texthook escreve para hookPipe.<br> O Host escreve para hostPipe, o texthook escreve para hookPipe.<br>
O texthook espera pelo pipe estar conectado e então injeta algumas intruções dentro de quaisquer funções que produzam texto (por exemplo: TextOut, GetGlyphOutline) o que faz com que seu produto seja mandado por meio do pipe.<br> O texthook espera pelo pipe estar conectado e então injeta algumas intruções dentro de quaisquer funções que produzam texto (por exemplo: TextOut, GetGlyphOutline) o que faz com que seu produto seja mandado por meio do pipe.<br>
Informação adicional sobre os hooks é trocada por meio da memória compartilhada.<br> Informação adicional sobre os hooks é trocada por meio da memória compartilhada.<br>
O texto que o host recebe por meio do pipe é então processado um pouco antes de ser despachado devolta para a IGU/GUI.<br> O texto que o host recebe por meio do pipe é então processado um pouco antes de ser despachado devolta para a IGU/GUI.<br>
Finalmente, a IGU/GUI despacha o texto para as extensões antes de mostrá-lo. Finalmente, a IGU/GUI despacha o texto para as extensões antes de mostrá-lo.
## [Desenvolvedores](docs/CREDITS.md) ## [Desenvolvedores](CREDITS.md)

View File

@ -2,15 +2,16 @@
![Как это выглядит](screenshot.png) ![Как это выглядит](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
**Textractor** (a.k.a. NextHooker) это проект x86/x64 Windows/Wine программы для захвата текста из видеоигр, основанный на [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br> **Textractor** (a.k.a. NextHooker) это проект x86/x64 Windows/Wine программы для захвата текста из видеоигр, основанный на [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
Смотреть [обучающее видео](docs/TUTORIAL.md) для быстрого ознакомления. Смотреть [обучающее видео](https://tinyurl.com/textractor-tutorial) для быстрого ознакомления.
## Загрузка ## Загрузка
Выпуски Textractor могут быть найдены [здесь](https://github.com/Artikash/Textractor/releases).<br> Выпуски Textractor могут быть найдены [здесь](https://github.com/Artikash/Textractor/releases).<br>
Последний выпуск ITHVNR может быть найден [здесь](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO). Последний выпуск ITHVNR может быть найден [здесь](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
Установите библиотеки Visual C redist(vcredist.x86.exe), если получаете ошибку при запуске Textractor.
## Возможности ## Возможности
@ -31,9 +32,9 @@
## Вклад ## Вклад
Любой вклад приветствуется! Пишите мне(автору) на akashmozumdar@gmail.com, если у вас есть любые вопросы о кодовой базе.<br> Любой вклад приветствуется! Пишите мне(автору)(нет, я не занят!) на akashmozumdar@gmail.com, если у вас есть любые вопросы о кодовой базе.<br>
Используйте стандартные действия для создания pull request (fork, branch, commit changes, создайте PR из своей ветки branch на мой master).<br> Используйте стандартные действия для создания pull request (fork, branch, commit changes, создайте PR из своей ветки branch на мой master).<br>
Вклад в перевод совсем не сложен: просто переведите строки в [text.cpp](text.cpp), также, как и этот README. Вклад в перевод совсем не сложен: просто переведите строки в text.cpp, также, как и этот README.
## Компиляция ## Компиляция
@ -42,11 +43,11 @@
## Архитектура проекта ## Архитектура проекта
Хост (смотрите папку host) внедряет texthook.dll (созданной из папки texthook) в целевой процесс и подключается к нему через два файла-канала (pipe).<br> Хост (смотрите папку GUI/host) внедряет texthook.dll (созданной из папки texthook) в целевой процесс и подключается к нему через два файла-канала (pipe).<br>
Хост пишет в hostPipe, texthook пишет в hookPipe.<br> Хост пишет в hostPipe, texthook пишет в hookPipe.<br>
texthook ждет присоединения канала, тогда внедряет некоторые инструкции в любые выводящие текст функции (такие как TextOut, GetGlyphOutline), что вызывает пересылку поступающего в них текста через канал.<br> texthook ждет присоединения канала, тогда внедряет некоторые инструкции в любые выводящие текст функции (такие как TextOut, GetGlyphOutline), что вызывает пересылку поступающего в них текста через канал.<br>
Дополнительная информация о хуках размещена через файл просмотра (a.k.a. section object), который сопоставлен с ссылкой на класс TextHook.<br> Дополнительная информация о хуках размещена через файл просмотра (a.k.a. section object), который сопоставлен с ссылкой на класс TextHook.<br>
Текст, который хост получает через канал, затем немного обрабатывается перед отправкой обратно в графический интерфейс (GUI).<br> Текст, который хост получает через канал, затем немного обрабатывается перед отправкой обратно в графический интерфейс (GUI).<br>
Наконец, GUI отправляет текст расширениям, перед его отображением. Наконец, GUI отправляет текст расширениям, перед его отображением.
## [Разработчики](docs/CREDITS.md) ## [Разработчики](CREDITS.md)

View File

@ -1,6 +1,6 @@
# Textractor # Textractor
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
## 概述 ## 概述
@ -28,9 +28,9 @@ Textractor 的发行版可以在[这里](https://github.com/Artikash/Textractor/
## 贡献 ## 贡献
欢迎一切贡献!如有任何关于代码的疑问,请向 akashmozumdar@gmail.com 发邮件.<br> 欢迎一切贡献!如有任何关于代码的疑问,请向 akashmozumdar@gmail.com 发邮件 (不,我并不忙!).<br>
你应当使用创建 PR 的标准过程 (分岔 (fork), 分支 (branch), 提交变化, 创建从你的分支到我的 master 分支的 PR).<br> 你应当使用创建 PR 的标准过程 (分岔 (fork), 分支 (branch), 提交变化, 创建从你的分支到我的 master 分支的 PR).<br>
提供翻译贡献很简单: 只需翻译 [text.cpp](text.cpp) 中的字符串和这份 README 即可. 提供翻译贡献很简单: 只需翻译 text.cpp 中的字符串和这份 README 即可.
## 编译 ## 编译
@ -39,11 +39,11 @@ Textractor 的发行版可以在[这里](https://github.com/Artikash/Textractor/
## 项目架构 ## 项目架构
宿主 (位于 host 文件夹) 向目标进程注入 texthook.dll (由 texthook 文件夹创建) 并通过两个管道文件互联.<br> 宿主 (位于 GUI/host 文件夹) 向目标进程注入 texthook.dll (由 texthook 文件夹创建) 并通过两个管道文件互联.<br>
宿主向 hostPipe 写入, texthook 向 hookPipe 写入.<br> 宿主向 hostPipe 写入, texthook 向 hookPipe 写入.<br>
texthook 等待管道连接, 之后向一些文本输出函数 (如 TextOut, GetGlyphOutline) 注入一系列指令, 使得它们的输入被沿着管道发送.<br> texthook 等待管道连接, 之后向一些文本输出函数 (如 TextOut, GetGlyphOutline) 注入一系列指令, 使得它们的输入被沿着管道发送.<br>
其它关于钩子的信息通过一个被 TextHook 类保有引用的文件视图 (曾用名: 段对象) 共享.<br> 其它关于钩子的信息通过一个被 TextHook 类保有引用的文件视图 (曾用名: 段对象) 共享.<br>
之后, 宿主通过管道接收到的文本在传回 GUI 前被简单处理.<br> 之后, 宿主通过管道接收到的文本在传回 GUI 前被简单处理.<br>
最后, GUI 在显示文本前将其分发给扩展. 最后, GUI 在显示文本前将其分发给扩展.
## [开发者](docs/CREDITS.md) ## [开发者](CREDITS.md)

View File

@ -2,16 +2,17 @@
![How it looks](screenshot.png) ![How it looks](screenshot.png)
[English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa Indonesia](README_ID.md) ● [Português](README_PT.md) ● [Deutsch](README_DE.md) [English](README.md) ● [Español](README_ES.md) ● [简体中文](README_SC.md) ● [Русский](README_RU.md) ● [한국어](README_KR.md) ● [ภาษาไทย](README_TH.md) ● [Français](README_FR.md) ● [Italiano](README_IT.md) ● [日本語](README_JP.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md)
**Textractor** **Textractor**
(หรือ NextHooker) คือโปรแกรมโอเพนซอร์ซสำหรับปฏิบัติการที่มีหน้าที่เพื่อเชื่อมกับตัวอักษรกับเกมจากที่มาจากระบบปฏิบัติการ Window/Wine โดยมีแบบดังเดิมมาจาก [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br> (หรือ NextHooker) คือโปรแกรมโอเพนซอร์ซสำหรับปฏิบัติการที่มีหน้าที่เพื่อเชื่อมกับตัวอักษรกับเกมจากที่มาจากระบบปฏิบัติการ Window/Wine โดยมีแบบดังเดิมมาจาก [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
สามารถดูตัวอย่างวิธีการใช้งาน [วีดีโอตัวอย่างการใช้งาน](docs/TUTORIAL.md) เพื่อที่จะแสดงความเข้าใจคร่าวๆเกี่ยวกับโปรแกรม. สามารถดูตัวอย่างวิธีการใช้งาน [วีดีโอตัวอย่างการใช้งาน](https://tinyurl.com/textractor-tutorial) เพื่อที่จะแสดงความเข้าใจคร่าวๆเกี่ยวกับโปรแกรม.
## ดาวน์โหลด ## ดาวน์โหลด
Textractor รุ่นล่าสุดสามารถดาวน์โหลดจาก [ที่นี้](https://github.com/Artikash/Textractor/releases).<br> Textractor รุ่นล่าสุดสามารถดาวน์โหลดจาก [ที่นี้](https://github.com/Artikash/Textractor/releases).<br>
ITHVNR รุ่นสุดท้ายสามารถดาวน์โหลดได้ [ที่นี้](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO). ITHVNR รุ่นสุดท้ายสามารถดาวน์โหลดได้ [ที่นี้](https://drive.google.com/open?id=13aHF4uIXWn-3YML_k2YCDWhtGgn5-tnO).<br>
ถ้าหากมีปัญหาขณะที่เปิด Textractor ลองเปิด vcredist
## คุณสมบัติ ## คุณสมบัติ
@ -37,7 +38,7 @@ ITHVNR รุ่นสุดท้ายสามารถดาวน์โห
## โครงสร้างโปรแกรม ## โครงสร้างโปรแกรม
ฐานของโปรแกรม (โฟลเดอร์ host) ส่งข้อมูลจาก texthook.dll (ที่ถูกสร้างจาก texthook โฟลเดอร์) ไปยังเกมเป้าหมาย และ เชื่อมทั่งสองอย่างเข้าด้วยกัน<br> ฐานของโปรแกรม (โฟลเดอร์ GUI/host) ส่งข้อมูลจาก texthook.dll (ที่ถูกสร้างจาก texthook โฟลเดอร์) ไปยังเกมเป้าหมาย และ เชื่อมทั่งสองอย่างเข้าด้วยกัน<br>
ฐานโปรแกรมเขียนผ่านฝั่ง hostPipe(ท่อเชื่อมฝั่งฐานข้อมูล) ในขณะที่ตัวดึงตัวอักษรที่ทางฝั่ง hookPipe(ท่อเชื่อมฝั่งดึงข้อมูล).<br> ฐานโปรแกรมเขียนผ่านฝั่ง hostPipe(ท่อเชื่อมฝั่งฐานข้อมูล) ในขณะที่ตัวดึงตัวอักษรที่ทางฝั่ง hookPipe(ท่อเชื่อมฝั่งดึงข้อมูล).<br>
ตัวดึงตัวอักษรรอการเชื่อมเข้ากับของทั่งสองท่อ หลังจากนั่นส่งคำสั่งไปยังข้อมูลนั่น (เช่น แสดงผลข้อมูล เป็นต้น) และทำให้ข้อมูลส่งผ่านต่อมาออกมาได้ถูกต้อง<br> ตัวดึงตัวอักษรรอการเชื่อมเข้ากับของทั่งสองท่อ หลังจากนั่นส่งคำสั่งไปยังข้อมูลนั่น (เช่น แสดงผลข้อมูล เป็นต้น) และทำให้ข้อมูลส่งผ่านต่อมาออกมาได้ถูกต้อง<br>
ข้อมูลบางอย่างเกี่ยวกับการเชื่อมจะถูกแลกเปลี่ยนผ่านความทรงจำของระบบ (shared memory) ข้อมูลบางอย่างเกี่ยวกับการเชื่อมจะถูกแลกเปลี่ยนผ่านความทรงจำของระบบ (shared memory)
@ -45,4 +46,4 @@ ITHVNR รุ่นสุดท้ายสามารถดาวน์โห
ตัวอักษรที่ฐานโปรแกรมรับผ่านท่อจะถูกแปลงเล็กน้อยก่อนที่จะแสดงผ่าน GUI <br> ตัวอักษรที่ฐานโปรแกรมรับผ่านท่อจะถูกแปลงเล็กน้อยก่อนที่จะแสดงผ่าน GUI <br>
สุดท้ายแล้ว GUI จะส่งข้อมูลตัวอักษรไปยังส่วนขยายต่างๆก่อนที่จะแสดงให้เห็นในหน้าจอ สุดท้ายแล้ว GUI จะส่งข้อมูลตัวอักษรไปยังส่วนขยายต่างๆก่อนที่จะแสดงให้เห็นในหน้าจอ
## [นักพัฒนา](docs/CREDITS.md) ## [นักพัฒนา](CREDITS.md)

View File

@ -1,14 +1,12 @@
macro(msvc_registry_search) macro(msvc_registry_search)
if(NOT DEFINED Qt5_DIR) if(NOT DEFINED Qt5_DIR AND MSVC)
if (NOT EXISTS ${QT_ROOT}) # look for user-registry pointing to qtcreator
# look for user-registry pointing to qtcreator get_filename_component(QT_BIN [HKEY_CURRENT_USER\\Software\\Classes\\Applications\\QtProject.QtCreator.pro\\shell\\Open\\Command] PATH)
get_filename_component(QT_ROOT [HKEY_CURRENT_USER\\Software\\Classes\\Applications\\QtProject.QtCreator.pro\\shell\\Open\\Command] PATH)
# get root path # get root path so we can search for 5.3, 5.4, 5.5, etc
string(REPLACE "/Tools" ";" QT_ROOT "${QT_ROOT}") string(REPLACE "/Tools" ";" QT_BIN "${QT_BIN}")
list(GET QT_ROOT 0 QT_ROOT) list(GET QT_BIN 0 QT_BIN)
endif() file(GLOB QT_VERSIONS "${QT_BIN}/5.1*")
file(GLOB QT_VERSIONS "${QT_ROOT}/5.13*")
list(SORT QT_VERSIONS) list(SORT QT_VERSIONS)
# assume the latest version will be last alphabetically # assume the latest version will be last alphabetically
@ -54,7 +52,7 @@ macro(find_qt5)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
#set(CMAKE_AUTOMOC ON) #set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
#add_definitions(-DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x060000) add_definitions(-DQT_DEPRECATED_WARNINGS -DQT_DISABLE_DEPRECATED_BEFORE=0x060000)
find_package(Qt5 COMPONENTS ${ARGN}) find_package(Qt5 COMPONENTS ${ARGN})
if(Qt5_FOUND) if(Qt5_FOUND)

View File

@ -45,14 +45,11 @@ foreach ($language in @{
"Copy to Clipboard", "Copy to Clipboard",
"DeepL Translate", "DeepL Translate",
"DevTools DeepL Translate", "DevTools DeepL Translate",
"DevTools Papago Translate",
"DevTools Systran Translate",
"Extra Newlines", "Extra Newlines",
"Extra Window", "Extra Window",
"Google Translate", "Google Translate",
"Lua", "Lua",
"Regex Filter", "Regex Filter",
"Regex Replacer",
"Remove Repeated Characters", "Remove Repeated Characters",
"Remove Repeated Phrases", "Remove Repeated Phrases",
"Remove Repeated Phrases 2", "Remove Repeated Phrases 2",
@ -68,6 +65,7 @@ foreach ($language in @{
&"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /v /t "http://timestamp.digicert.com" /fd SHA256 @(dir "$folder\**\*"); &"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /v /t "http://timestamp.digicert.com" /fd SHA256 @(dir "$folder\**\*");
} }
rm -Force -Recurse -Verbose "Runtime"; rm -Force -Recurse -Verbose "Runtime";
mkdir -Force -Verbose "Runtime"; mkdir -Force -Verbose "Runtime";
foreach ($arch in @("x86", "x64")) foreach ($arch in @("x86", "x64"))
@ -80,7 +78,6 @@ foreach ($arch in @("x86", "x64"))
"Qt5Gui.dll", "Qt5Gui.dll",
"Qt5Network.dll", "Qt5Network.dll",
"Qt5WebSockets.dll", "Qt5WebSockets.dll",
"Qt5WinExtras.dll"
"Qt5Widgets.dll", "Qt5Widgets.dll",
"platforms", "platforms",
"styles" "styles"
@ -88,13 +85,13 @@ foreach ($arch in @("x86", "x64"))
{ {
copy -Force -Recurse -Verbose -Destination "Runtime/$arch/$file" -Path "Release_$arch/$file"; copy -Force -Recurse -Verbose -Destination "Runtime/$arch/$file" -Path "Release_$arch/$file";
} }
copy -Force -Recurse -Verbose -Destination "Runtime/$arch" -Path "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Redist/MSVC/**/$arch/Microsoft.VC142.CRT/*" copy -Force -Recurse -Verbose -Destination "$arch" -Path "C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Redist/MSVC/**/$arch/Microsoft.VC142.CRT/*"
} }
rm -Force -Recurse -Verbose "Textractor"; rm -Force -Recurse -Verbose "Textractor";
mkdir -Force -Verbose "Textractor"; mkdir -Force -Verbose "Textractor";
copy -Force -Recurse -Verbose -Destination "Textractor" -Path @("Runtime/*", "Textractor--$version/*"); copy -Force -Recurse -Verbose -Destination "Textractor" -Path @("Runtime/*", "Textractor--$version/*");
&"C:\Program Files\7-Zip\7z" a "Textractor-$version-Zip-Version-English-Only.zip" Textractor/ ../INSTALL_THIS_UNICODE_FONT.ttf &"C:\Program Files\7-Zip\7z" a "Textractor-$version-Zip-Version-English-Only.zip" Textractor/
cd ..; cd ..;
&"C:\Program Files (x86)\Inno Setup 6\iscc" -DVERSION="$version" installer.iss; &"C:\Program Files (x86)\Inno Setup 6\iscc" -DVERSION="$version" installer.iss;

View File

@ -1,7 +0,0 @@
# Tutorial Video
https://www.youtube.com/watch?v=eecEOacF6mw
## Updates/Corrections
Automatically finding hooks is now done via the `Search for hooks` button, the method shown in the video is found in the `Search for specific text` option.

View File

@ -1,17 +1,18 @@
include(QtUtils)
msvc_registry_search()
find_qt5(Core Widgets WebSockets)
cmake_policy(SET CMP0037 OLD) cmake_policy(SET CMP0037 OLD)
add_library(Bing\ Translate MODULE bingtranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) add_library(Bing\ Translate MODULE bingtranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(Copy\ to\ Clipboard MODULE copyclipboard.cpp extensionimpl.cpp) add_library(Copy\ to\ Clipboard MODULE copyclipboard.cpp extensionimpl.cpp)
add_library(DeepL\ Translate MODULE deepltranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) add_library(DeepL\ Translate MODULE deepltranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(DevTools\ DeepL\ Translate MODULE devtoolsdeepltranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) add_library(DevTools\ DeepL\ Translate MODULE devtoolsdeepltranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(DevTools\ Papago\ Translate MODULE devtoolspapagotranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(DevTools\ Systran\ Translate MODULE devtoolssystrantranslate.cpp devtools.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp) add_library(Extra\ Newlines MODULE extranewlines.cpp extensionimpl.cpp)
add_library(Extra\ Window MODULE extrawindow.cpp extensionimpl.cpp) add_library(Extra\ Window MODULE extrawindow.cpp extensionimpl.cpp)
add_library(Google\ Translate MODULE googletranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp) add_library(Google\ Translate MODULE googletranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(Libre\ Translate MODULE libretranslate.cpp translatewrapper.cpp network.cpp extensionimpl.cpp)
add_library(Lua MODULE lua.cpp extensionimpl.cpp) add_library(Lua MODULE lua.cpp extensionimpl.cpp)
add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp) add_library(Regex\ Filter MODULE regexfilter.cpp extensionimpl.cpp)
add_library(Regex\ Replacer MODULE regexreplacer.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\ 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)
@ -24,14 +25,12 @@ target_precompile_headers(Bing\ Translate REUSE_FROM pch)
target_precompile_headers(Copy\ to\ Clipboard REUSE_FROM pch) target_precompile_headers(Copy\ to\ Clipboard REUSE_FROM pch)
target_precompile_headers(DeepL\ Translate REUSE_FROM pch) target_precompile_headers(DeepL\ Translate REUSE_FROM pch)
target_precompile_headers(DevTools\ DeepL\ Translate REUSE_FROM pch) target_precompile_headers(DevTools\ DeepL\ Translate REUSE_FROM pch)
target_precompile_headers(DevTools\ Papago\ Translate REUSE_FROM pch)
target_precompile_headers(DevTools\ Systran\ Translate REUSE_FROM pch)
target_precompile_headers(Extra\ Newlines REUSE_FROM pch) target_precompile_headers(Extra\ Newlines REUSE_FROM pch)
target_precompile_headers(Extra\ Window REUSE_FROM pch) target_precompile_headers(Extra\ Window REUSE_FROM pch)
target_precompile_headers(Google\ Translate REUSE_FROM pch) target_precompile_headers(Google\ Translate REUSE_FROM pch)
target_precompile_headers(Libre\ Translate REUSE_FROM pch)
target_precompile_headers(Lua REUSE_FROM pch) target_precompile_headers(Lua REUSE_FROM pch)
target_precompile_headers(Regex\ Filter REUSE_FROM pch) target_precompile_headers(Regex\ Filter REUSE_FROM pch)
target_precompile_headers(Regex\ Replacer 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\ 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)
@ -43,10 +42,9 @@ target_precompile_headers(Thread\ Linker REUSE_FROM pch)
target_link_libraries(Bing\ Translate winhttp Qt5::Widgets) target_link_libraries(Bing\ Translate winhttp Qt5::Widgets)
target_link_libraries(DeepL\ Translate winhttp Qt5::Widgets) target_link_libraries(DeepL\ Translate winhttp Qt5::Widgets)
target_link_libraries(DevTools\ DeepL\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets) target_link_libraries(DevTools\ DeepL\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets)
target_link_libraries(DevTools\ Papago\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets)
target_link_libraries(DevTools\ Systran\ Translate shell32 winhttp Qt5::Widgets Qt5::WebSockets)
target_link_libraries(Extra\ Window Qt5::Widgets) target_link_libraries(Extra\ Window Qt5::Widgets)
target_link_libraries(Google\ Translate winhttp Qt5::Widgets) target_link_libraries(Google\ Translate winhttp Qt5::Widgets)
target_link_libraries(Libre\ Translate winhttp Qt5::Widgets)
target_link_libraries(Lua lua53 Qt5::Widgets) target_link_libraries(Lua lua53 Qt5::Widgets)
target_link_libraries(Regex\ Filter Qt5::Widgets) target_link_libraries(Regex\ Filter Qt5::Widgets)
target_link_libraries(Styler Qt5::Widgets) target_link_libraries(Styler Qt5::Widgets)

View File

@ -1,241 +1,113 @@
#include "qtcommon.h" #include "qtcommon.h"
#include "translatewrapper.h"
#include "network.h" #include "network.h"
extern const wchar_t* TRANSLATION_ERROR; extern const wchar_t* TRANSLATION_ERROR;
extern Synchronized<std::wstring> translateTo, translateFrom, authKey;
const char* TRANSLATION_PROVIDER = "Bing Translate"; const char* TRANSLATION_PROVIDER = "Bing Translate";
const char* GET_API_KEY_FROM = "https://www.microsoft.com/en-us/translator/business/trial/#get-started"; const char* GET_API_KEY_FROM = "https://www.microsoft.com/en-us/translator/business/trial/#get-started";
extern const QStringList languagesTo QStringList languages
{ {
"Afrikaans", "Afrikaans: af",
"Albanian", "Arabic: ar",
"Amharic", "Bangla: bn",
"Arabic", "Bosnian: bs",
"Armenian", "Bulgarian: bg",
"Assamese", "Cantonese (traditional): yue",
"Azerbaijani", "Catalan: ca",
"Bangla", "Chinese (simplified): zh-Hans",
"Bosnian (Latin)", "Chinese (traditional): zh-Hant",
"Bulgarian", "Croatian: hr",
"Cantonese (Traditional)", "Czech: cs",
"Catalan", "Danish: da",
"Chinese (Simplified)", "Dutch: nl",
"Chinese (Traditional)", "English: en",
"Croatian", "Estonian: et",
"Czech", "Fijian: fj",
"Danish", "Filipino: fil",
"Dari", "Finnish: fi",
"Dutch", "French: fr",
"English", "German: de",
"Estonian", "Greek: el",
"Fijian", "Haitian Creole: ht",
"Filipino", "Hebrew: he",
"Finnish", "Hindi: hi",
"French", "Hmong Daw: mww",
"French (Canada)", "Hungarian: hu",
"German", "Icelandic: is",
"Greek", "Indonesian: id",
"Gujarati", "Irish: ga",
"Haitian Creole", "Italian: it",
"Hebrew", "Japanese: ja",
"Hindi", "Kannada: kn",
"Hmong Daw", "Klingon: tlh",
"Hungarian", "Korean: ko",
"Icelandic", "Latvian: lv",
"Indonesian", "Lithuanian: lt",
"Inuktitut", "Malagasy: mg",
"Irish", "Malay: ms",
"Italian", "Malayalam: ml",
"Japanese", "Maltese: mt",
"Kannada", "Maori: mi",
"Kazakh", "Norwegian: nb",
"Khmer", "Persian: fa",
"Klingon", "Polish: pl",
"Korean", "Portuguese (Brazil): pt",
"Kurdish (Central)", "Portuguese (Portugal): pt-pt",
"Kurdish (Northern)", "Punjabi: pa",
"Lao", "Romanian: ro",
"Latvian", "Russian: ru",
"Lithuanian", "Samoan: sm",
"Malagasy", "Serbian (Cyrillic): sr-Cyrl",
"Malay", "Serbian (Latin): sr-Latn",
"Malayalam", "Slovak: sk",
"Maltese", "Slovenian: sl",
"Maori", "Spanish: es",
"Marathi", "Swahili: sw",
"Myanmar", "Swedish: sv",
"Nepali", "Tahitian: ty",
"Norwegian", "Tamil: ta",
"Odia", "Telugu: te",
"Pashto", "Thai: th",
"Persian", "Tongan: to",
"Polish", "Turkish: tr",
"Portuguese (Brazil)", "Ukrainian: uk",
"Portuguese (Portugal)", "Urdu: ur",
"Punjabi", "Vietnamese: vi",
"Queretaro Otomi", "Welsh: cy",
"Romanian", "Yucatec Maya: yua"
"Russian",
"Samoan",
"Serbian (Cyrillic)",
"Serbian (Latin)",
"Slovak",
"Slovenian",
"Spanish",
"Swahili",
"Swedish",
"Tahitian",
"Tamil",
"Telugu",
"Thai",
"Tigrinya",
"Tongan",
"Turkish",
"Ukrainian",
"Urdu",
"Vietnamese",
"Welsh",
"Yucatec Maya"
}, 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"Assamese" }, { L"as" } },
{ { L"Azerbaijani" }, { L"az" } },
{ { L"Bangla" }, { L"bn" } },
{ { L"Bosnian (Latin)" }, { L"bs" } },
{ { L"Bulgarian" }, { L"bg" } },
{ { L"Cantonese (Traditional)" }, { L"yue" } },
{ { L"Catalan" }, { L"ca" } },
{ { L"Chinese (Simplified)" }, { L"zh-Hans" } },
{ { L"Chinese (Traditional)" }, { L"zh-Hant" } },
{ { L"Croatian" }, { L"hr" } },
{ { L"Czech" }, { L"cs" } },
{ { L"Danish" }, { L"da" } },
{ { L"Dari" }, { L"prs" } },
{ { L"Dutch" }, { L"nl" } },
{ { L"English" }, { L"en" } },
{ { L"Estonian" }, { L"et" } },
{ { L"Fijian" }, { L"fj" } },
{ { L"Filipino" }, { L"fil" } },
{ { L"Finnish" }, { L"fi" } },
{ { L"French" }, { L"fr" } },
{ { L"French (Canada)" }, { L"fr-ca" } },
{ { L"German" }, { L"de" } },
{ { L"Greek" }, { L"el" } },
{ { L"Gujarati" }, { L"gu" } },
{ { L"Haitian Creole" }, { L"ht" } },
{ { L"Hebrew" }, { L"he" } },
{ { L"Hindi" }, { L"hi" } },
{ { L"Hmong Daw" }, { L"mww" } },
{ { L"Hungarian" }, { L"hu" } },
{ { L"Icelandic" }, { L"is" } },
{ { L"Indonesian" }, { L"id" } },
{ { L"Inuktitut" }, { L"iu" } },
{ { L"Irish" }, { L"ga" } },
{ { L"Italian" }, { L"it" } },
{ { L"Japanese" }, { L"ja" } },
{ { L"Kannada" }, { L"kn" } },
{ { L"Kazakh" }, { L"kk" } },
{ { L"Khmer" }, { L"km" } },
{ { L"Klingon" }, { L"tlh-Latn" } },
{ { L"Korean" }, { L"ko" } },
{ { L"Kurdish (Central)" }, { L"ku" } },
{ { L"Kurdish (Northern)" }, { L"kmr" } },
{ { L"Lao" }, { L"lo" } },
{ { L"Latvian" }, { L"lv" } },
{ { L"Lithuanian" }, { L"lt" } },
{ { 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"Myanmar" }, { L"my" } },
{ { L"Nepali" }, { L"ne" } },
{ { L"Norwegian" }, { L"nb" } },
{ { L"Odia" }, { L"or" } },
{ { L"Pashto" }, { L"ps" } },
{ { L"Persian" }, { L"fa" } },
{ { L"Polish" }, { L"pl" } },
{ { L"Portuguese (Brazil)" }, { L"pt" } },
{ { L"Portuguese (Portugal)" }, { L"pt-pt" } },
{ { L"Punjabi" }, { L"pa" } },
{ { L"Queretaro Otomi" }, { L"otq" } },
{ { L"Romanian" }, { L"ro" } },
{ { L"Russian" }, { L"ru" } },
{ { L"Samoan" }, { L"sm" } },
{ { L"Serbian (Cyrillic)" }, { L"sr-Cyrl" } },
{ { L"Serbian (Latin)" }, { L"sr-Latn" } },
{ { L"Slovak" }, { L"sk" } },
{ { L"Slovenian" }, { L"sl" } },
{ { L"Spanish" }, { L"es" } },
{ { L"Swahili" }, { L"sw" } },
{ { L"Swedish" }, { L"sv" } },
{ { L"Tahitian" }, { L"ty" } },
{ { L"Tamil" }, { L"ta" } },
{ { L"Telugu" }, { L"te" } },
{ { L"Thai" }, { L"th" } },
{ { L"Tigrinya" }, { L"ti" } },
{ { L"Tongan" }, { L"to" } },
{ { L"Turkish" }, { L"tr" } },
{ { L"Ukrainian" }, { L"uk" } },
{ { L"Urdu" }, { L"ur" } },
{ { L"Vietnamese" }, { L"vi" } },
{ { L"Welsh" }, { L"cy" } },
{ { L"Yucatec Maya" }, { L"yua" } },
{ { L"?" }, { L"auto-detect" } }
}; };
std::wstring autoDetectLanguage = L"auto-detect";
bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true; bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000; int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp) std::pair<bool, std::wstring> Translate(const std::wstring& text)
{ {
if (!tlp.authKey.empty()) if (!authKey->empty())
{ {
std::wstring translateFromComponent = tlp.translateFrom == L"?" ? L"" : L"&from=" + codes.at(tlp.translateFrom); std::wstring translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? L"" : L"&from=" + translateFrom.Copy();
if (HttpRequest httpRequest{ if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor", L"Mozilla/5.0 Textractor",
L"api.cognitive.microsofttranslator.com", L"api.cognitive.microsofttranslator.com",
L"POST", L"POST",
FormatString(L"/translate?api-version=3.0&to=%s%s", codes.at(tlp.translateTo), translateFromComponent).c_str(), FormatString(L"/translate?api-version=3.0&to=%s%s", translateTo.Copy(), translateFromComponent).c_str(),
FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))), FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))),
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", tlp.authKey).c_str() FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", authKey.Copy()).c_str()
}) })
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() }; if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) }; else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) }; else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
} }
static std::atomic<int> i = 0;
static Synchronized<std::wstring> token;
if (token->empty()) if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"www.bing.com", L"GET", L"translator" })
{
std::wstring tokenBuilder;
if (auto tokenPos = httpRequest.response.find(L"[" + std::to_wstring(time(nullptr) / 100)); tokenPos != std::string::npos)
tokenBuilder = FormatString(L"&key=%s&token=%s", httpRequest.response.substr(tokenPos + 1, 13), httpRequest.response.substr(tokenPos + 16, 32));
if (auto tokenPos = httpRequest.response.find(L"IG:\""); tokenPos != std::string::npos)
tokenBuilder += L"&IG=" + httpRequest.response.substr(tokenPos + 4, 32);
if (auto tokenPos = httpRequest.response.find(L"data-iid=\""); tokenPos != std::string::npos)
tokenBuilder += L"&IID=" + httpRequest.response.substr(tokenPos + 10, 15);
if (!tokenBuilder.empty()) token->assign(tokenBuilder);
else return { false, FormatString(L"%s: %s\ntoken not found", TRANSLATION_ERROR, httpRequest.response) };
}
else return { false, FormatString(L"%s: could not acquire token", TRANSLATION_ERROR) };
if (HttpRequest httpRequest{ if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor", L"Mozilla/5.0 Textractor",
L"www.bing.com", L"www.bing.com",
L"POST", L"POST",
FormatString(L"/ttranslatev3?fromLang=%s&to=%s&text=%s%s.%d", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text), token.Copy(), i++).c_str() FormatString(L"/ttranslatev3?fromLang=%s&to=%s&text=%s", translateFrom.Copy(), translateTo.Copy(), Escape(text)).c_str()
}) })
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() }; if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
else return { false, FormatString(L"%s (token=%s): %s", TRANSLATION_ERROR, std::exchange(token.Acquire().contents, L""), httpRequest.response) }; else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) }; else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
} }

View File

@ -49,7 +49,7 @@ private:
} }
static constexpr C endImpl[5] = { '|', 'E', 'N', 'D', '|' }; static constexpr C endImpl[5] = { '|', 'E', 'N', 'D', '|' };
static constexpr std::basic_string_view<C> end{ endImpl, 5 }; static constexpr std::basic_string_view end{ endImpl, 5 };
std::basic_streambuf<char>& streambuf; std::basic_streambuf<char>& streambuf;
std::basic_string<C> buffer; std::basic_string<C> buffer;

View File

@ -1,132 +1,54 @@
#include "qtcommon.h" #include "qtcommon.h"
#include "translatewrapper.h"
#include "network.h" #include "network.h"
#include <random> #include <random>
extern const wchar_t* TRANSLATION_ERROR; extern const wchar_t* TRANSLATION_ERROR;
extern const char* USE_PREV_SENTENCE_CONTEXT;
extern Synchronized<std::wstring> translateTo, translateFrom, authKey;
const char* TRANSLATION_PROVIDER = "DeepL Translate"; const char* TRANSLATION_PROVIDER = "DeepL Translate";
const char* GET_API_KEY_FROM = "https://www.deepl.com/pro.html#developer"; const char* GET_API_KEY_FROM = "https://www.deepl.com/pro.html";
extern const QStringList languagesTo QStringList languages
{ {
"Bulgarian", "Chinese: ZH",
"Chinese (Simplified)", "Dutch: NL",
"Czech", "English: EN",
"Danish", "French: FR",
"Dutch", "German: DE",
"English (American)", "Italian: IT",
"English (British)", "Japanese: JA",
"Estonian", "Polish: PL",
"Finnish", "Portuguese: PT",
"French", "Russian: RU",
"German", "Spanish: ES",
"Greek",
"Hungarian",
"Indonesian",
"Italian",
"Japanese",
"Latvian",
"Lithuanian",
"Polish",
"Portuguese (Brazil)",
"Portuguese (Portugal)",
"Romanian",
"Russian",
"Slovak",
"Slovenian",
"Spanish",
"Swedish",
"Turkish"
},
languagesFrom
{
"Bulgarian",
"Chinese",
"Czech",
"Danish",
"Dutch",
"English",
"Estonian",
"Finnish",
"French",
"German",
"Greek",
"Hungarian",
"Indonesian",
"Italian",
"Japanese",
"Latvian",
"Lithuanian",
"Polish",
"Portuguese",
"Romanian",
"Russian",
"Slovak",
"Slovenian",
"Spanish",
"Swedish",
"Turkish"
};
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Bulgarian" }, { L"BG" } },
{ { L"Chinese" }, { L"ZH" } },
{ { L"Chinese (Simplified)" }, { L"ZH" } },
{ { L"Czech" }, { L"CS" } },
{ { L"Danish" }, { L"DA" } },
{ { L"Dutch" }, { L"NL" } },
{ { L"English" }, { L"EN" } },
{ { L"English (American)" }, { L"EN-US" } },
{ { L"English (British)" }, { L"EN-GB" } },
{ { L"Estonian" }, { L"ET" } },
{ { L"Finnish" }, { L"FI" } },
{ { L"French" }, { L"FR" } },
{ { L"German" }, { L"DE" } },
{ { L"Greek" }, { L"EL" } },
{ { L"Hungarian" }, { L"HU" } },
{ { L"Indonesian" }, { L"ID" } },
{ { L"Italian" }, { L"IT" } },
{ { L"Japanese" }, { L"JA" } },
{ { L"Latvian" }, { L"LV" } },
{ { L"Lithuanian" }, { L"LT" } },
{ { L"Polish" }, { L"PL" } },
{ { L"Portuguese" }, { L"PT" } },
{ { L"Portuguese (Brazil)" }, { L"PT-BR" } },
{ { L"Portuguese (Portugal)" }, { L"PT-PT" } },
{ { L"Romanian" }, { L"RO" } },
{ { L"Russian" }, { L"RU" } },
{ { L"Slovak" }, { L"SK" } },
{ { L"Slovenian" }, { L"SL" } },
{ { L"Spanish" }, { L"ES" } },
{ { L"Swedish" }, { L"SV" } },
{ { L"Turkish" }, { L"TR" } },
{ { L"?" }, { L"auto" } }
}; };
std::wstring autoDetectLanguage = L"auto";
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = true, useCache = true, useFilter = true; bool translateSelectedOnly = true, rateLimitAll = true, rateLimitSelected = true, useCache = true, useFilter = true;
int tokenCount = 10, rateLimitTimespan = 60000, maxSentenceSize = 1000; int tokenCount = 10, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
enum KeyType { CAT, REST }; enum KeyType { CAT, REST };
int keyType = REST; int keyType = CAT;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp) std::pair<bool, std::wstring> Translate(const std::wstring& text)
{ {
if (!tlp.authKey.empty()) if (!authKey->empty())
{ {
std::string translateFromComponent = tlp.translateFrom == L"?" ? "" : "&source_lang=" + WideStringToString(codes.at(tlp.translateFrom)); std::string translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? "" : "&source_lang=" + WideStringToString(translateFrom.Copy());
if (HttpRequest httpRequest{ if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor", L"Mozilla/5.0 Textractor",
tlp.authKey.find(L":fx") == std::string::npos ? L"api.deepl.com" : L"api-free.deepl.com", L"api.deepl.com",
L"POST", L"POST",
keyType == CAT ? L"/v1/translate" : L"/v2/translate", keyType == CAT ? L"/v1/translate" : L"/v2/translate",
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), tlp.authKey, codes.at(tlp.translateTo)) + translateFromComponent, FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()) + translateFromComponent,
L"Content-Type: application/x-www-form-urlencoded" L"Content-Type: application/x-www-form-urlencoded"
}; httpRequest && (httpRequest.response.find(L"translations") != std::string::npos || (httpRequest = HttpRequest{ }; httpRequest && (!httpRequest.response.empty() || (httpRequest = HttpRequest{
L"Mozilla/5.0 Textractor", L"Mozilla/5.0 Textractor",
tlp.authKey.find(L":fx") == std::string::npos ? L"api.deepl.com" : L"api-free.deepl.com", L"api.deepl.com",
L"POST", L"POST",
(keyType = !keyType) == CAT ? L"/v1/translate" : L"/v2/translate", (keyType = !keyType) == CAT ? L"/v1/translate" : L"/v2/translate",
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), tlp.authKey, codes.at(tlp.translateTo)) + translateFromComponent, FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), authKey.Copy(), translateTo.Copy()) + translateFromComponent,
L"Content-Type: application/x-www-form-urlencoded" L"Content-Type: application/x-www-form-urlencoded"
}))) })))
// Response formatted as JSON: translation starts with text":" and ends with "}] // Response formatted as JSON: translation starts with text":" and ends with "}]
@ -136,8 +58,9 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationPar
} }
// the following code was reverse engineered from the DeepL website; it's as close as I could make it but I'm not sure what parts of this could be removed and still have it work // the following code was reverse engineered from the DeepL website; it's as close as I could make it but I'm not sure what parts of this could be removed and still have it work
int id = 10000 * std::uniform_int_distribution(0, 9999)(std::random_device()) + 1;
int64_t r = _time64(nullptr), n = std::count(text.begin(), text.end(), L'i') + 1; int64_t r = _time64(nullptr), n = std::count(text.begin(), text.end(), L'i') + 1;
thread_local auto generator = std::mt19937(std::random_device()());
int id = 10000 * std::uniform_int_distribution(0, 9999)(generator) + 1;
// user_preferred_langs? what should priority be? does timestamp do anything? other translation quality options? // user_preferred_langs? what should priority be? does timestamp do anything? other translation quality options?
auto body = FormatString(R"( auto body = FormatString(R"(
{ {
@ -148,7 +71,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationPar
"priority": -1, "priority": -1,
"timestamp": %lld, "timestamp": %lld,
"lang": { "lang": {
"target_lang": "%.2S", "target_lang": "%S",
"source_lang_user_selected": "%S" "source_lang_user_selected": "%S"
}, },
"jobs": [{ "jobs": [{
@ -161,7 +84,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationPar
}] }]
} }
} }
)", id, r + (n - r % n), codes.at(tlp.translateTo), codes.at(tlp.translateFrom), JSON::Escape(WideStringToString(text))); )", id, r + (n - r % n), translateTo.Copy(), translateFrom.Copy(), JSON::Escape(WideStringToString(text)));
// missing accept-encoding header since it fucks up HttpRequest // missing accept-encoding header since it fucks up HttpRequest
if (HttpRequest httpRequest{ if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor", L"Mozilla/5.0 Textractor",

View File

@ -1,85 +1,28 @@
#include "devtools.h" #include "devtools.h"
#include "module.h"
#include <ppltasks.h>
#include <ShlObj.h>
#include <QWebSocket> #include <QWebSocket>
#include <QMetaEnum> #include <QMetaEnum>
#include <QFileDialog> #include <ppltasks.h>
#include <QMouseEvent>
extern const char* CHROME_LOCATION;
extern const char* START_DEVTOOLS;
extern const char* STOP_DEVTOOLS;
extern const char* HIDE_CHROME;
extern const char* DEVTOOLS_STATUS;
extern const char* AUTO_START;
extern const char* TRANSLATION_PROVIDER;
extern QFormLayout* display;
extern Settings settings;
namespace namespace
{ {
QLabel* statusLabel; std::function<void(QString)> OnStatusChanged = Swallow;
AutoHandle<> process = NULL; PROCESS_INFORMATION processInfo = {};
QWebSocket webSocket;
std::atomic<int> idCounter = 0; std::atomic<int> idCounter = 0;
Synchronized<std::unordered_map<int, concurrency::task_completion_event<JSON::Value<wchar_t>>>> mapQueue; std::mutex devToolsMutex;
QWebSocket webSocket;
void StatusChanged(QString status) std::unordered_map<int, concurrency::task_completion_event<JSON::Value<wchar_t>>> mapQueue;
{
QMetaObject::invokeMethod(statusLabel, std::bind(&QLabel::setText, statusLabel, status));
}
void Start(std::wstring chromePath, bool headless)
{
if (process) DevTools::Close();
auto args = FormatString(
L"%s --proxy-server=direct:// --disable-extensions --disable-gpu --no-first-run --user-data-dir=\"%s\\devtoolscache\" --remote-debugging-port=9222",
chromePath,
std::filesystem::current_path().wstring()
);
args += headless ? L" --window-size=1920,1080 --headless" : L" --window-size=850,900";
DWORD exitCode = 0;
STARTUPINFOW DUMMY = { sizeof(DUMMY) };
PROCESS_INFORMATION processInfo = {};
if (!CreateProcessW(NULL, args.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &DUMMY, &processInfo)) return StatusChanged("StartupFailed");
CloseHandle(processInfo.hThread);
process = processInfo.hProcess;
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<wchar_t>& object) { return object[L"type"].String() && *object[L"type"].String() == L"page" && object[L"webSocketDebuggerUrl"].String(); }
); it != list->end()) return webSocket.open(S(*(*it)[L"webSocketDebuggerUrl"].String()));
StatusChanged("ConnectingFailed");
}
auto _ = ([] auto _ = ([]
{ {
QObject::connect(&webSocket, &QWebSocket::stateChanged, QObject::connect(&webSocket, &QWebSocket::stateChanged,
[](QAbstractSocket::SocketState state) { StatusChanged(QMetaEnum::fromType<QAbstractSocket::SocketState>().valueToKey(state)); }); [](QAbstractSocket::SocketState state) { OnStatusChanged(QMetaEnum::fromType<QAbstractSocket::SocketState>().valueToKey(state)); });
QObject::connect(&webSocket, &QWebSocket::textMessageReceived, [](QString message) QObject::connect(&webSocket, &QWebSocket::textMessageReceived, [](QString message)
{ {
auto result = JSON::Parse(S(message)); auto result = JSON::Parse(S(message));
auto mapQueue = ::mapQueue.Acquire(); std::scoped_lock lock(devToolsMutex);
if (auto id = result[L"id"].Number()) if (auto request = mapQueue->find((int)*id); request != mapQueue->end()) if (auto id = result[L"id"].Number()) if (auto request = mapQueue.find((int)*id); request != mapQueue.end())
{ {
request->second.set(result); request->second.set(result);
mapQueue->erase(request); mapQueue.erase(request);
} }
}); });
}(), 0); }(), 0);
@ -87,77 +30,70 @@ namespace
namespace DevTools namespace DevTools
{ {
void Initialize() void Start(const std::wstring& path, std::function<void(QString)> statusChanged, bool headless)
{ {
QString chromePath = settings.value(CHROME_LOCATION).toString(); OnStatusChanged = statusChanged;
if (chromePath.isEmpty()) 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)
)
{ {
for (auto [_, process] : GetAllProcesses()) if (HttpRequest httpRequest{
if (process && (process->find(L"\\chrome.exe") != std::string::npos || process->find(L"\\msedge.exe") != std::string::npos)) chromePath = S(process.value()); L"Mozilla/5.0 Textractor",
wchar_t programFiles[MAX_PATH + 100] = {}; L"127.0.0.1",
for (auto folder : { CSIDL_PROGRAM_FILESX86, CSIDL_PROGRAM_FILES, CSIDL_LOCAL_APPDATA }) L"POST",
L"/json/list",
"",
NULL,
9222,
NULL,
WINHTTP_FLAG_ESCAPE_DISABLE
})
{ {
SHGetFolderPathW(NULL, folder, NULL, SHGFP_TYPE_CURRENT, programFiles); if (auto list = Copy(JSON::Parse(httpRequest.response).Array())) if (auto it = std::find_if(
wcscat_s(programFiles, L"/Google/Chrome/Application/chrome.exe"); list->begin(),
if (std::filesystem::exists(programFiles)) chromePath = S(programFiles); list->end(),
[](const JSON::Value<wchar_t>& object) { return object[L"type"].String() && *object[L"type"].String() == L"page" && object[L"webSocketDebuggerUrl"].String(); }
); it != list->end())
{
std::scoped_lock lock(devToolsMutex);
webSocket.open(S(*(*it)[L"webSocketDebuggerUrl"].String()));
return;
}
} }
OnStatusChanged("Failed Connection");
} }
auto chromePathEdit = new QLineEdit(chromePath); else OnStatusChanged("Failed Startup");
static struct : QObject
{
bool eventFilter(QObject* object, QEvent* event)
{
if (auto mouseEvent = dynamic_cast<QMouseEvent*>(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(HIDE_CHROME, true).toBool());
QObject::connect(headlessCheck, &QCheckBox::clicked, [](bool headless) { settings.setValue(HIDE_CHROME, headless); });
QObject::connect(startButton, &QPushButton::clicked, [chromePathEdit, headlessCheck] { Start(S(chromePathEdit->text()), headlessCheck->isChecked()); });
QObject::connect(stopButton, &QPushButton::clicked, &Close);
auto buttons = new QHBoxLayout();
buttons->addWidget(startButton);
buttons->addWidget(stopButton);
display->addRow(HIDE_CHROME, 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 = new QLabel("Stopped");
statusLabel->setFrameStyle(QFrame::Panel | QFrame::Sunken);
display->addRow(DEVTOOLS_STATUS, statusLabel);
if (autoStartCheck->isChecked()) QMetaObject::invokeMethod(startButton, &QPushButton::click, Qt::QueuedConnection);
} }
void Close() void Close()
{ {
std::scoped_lock lock(devToolsMutex);
for (const auto& [_, task] : mapQueue) task.set_exception(std::runtime_error("closed"));
webSocket.close(); webSocket.close();
for (const auto& [_, task] : mapQueue.Acquire().contents) task.set_exception(std::runtime_error("closed")); mapQueue.clear();
mapQueue->clear(); DWORD exitCode = 0;
if (GetExitCodeProcess(processInfo.hProcess, &exitCode) && exitCode == STILL_ACTIVE)
if (process)
{ {
TerminateProcess(process, 0); TerminateProcess(processInfo.hProcess, 0);
WaitForSingleObject(process, 1000); WaitForSingleObject(processInfo.hProcess, 2000);
for (int retry = 0; ++retry < 20; Sleep(100)) CloseHandle(processInfo.hProcess);
try { std::filesystem::remove_all(L"devtoolscache"); break; } CloseHandle(processInfo.hThread);
catch (std::filesystem::filesystem_error) { continue; }
} }
process = NULL; try { std::filesystem::remove_all(L"devtoolscache"); } catch (std::filesystem::filesystem_error) {}
StatusChanged("Stopped"); OnStatusChanged("Stopped");
} }
bool Connected() bool Connected()
{ {
std::scoped_lock lock(devToolsMutex);
return webSocket.state() == QAbstractSocket::ConnectedState; return webSocket.state() == QAbstractSocket::ConnectedState;
} }
@ -165,9 +101,14 @@ namespace DevTools
{ {
concurrency::task_completion_event<JSON::Value<wchar_t>> response; concurrency::task_completion_event<JSON::Value<wchar_t>> response;
int id = idCounter += 1; int id = idCounter += 1;
if (!Connected()) return {}; auto message = FormatString(LR"({"id":%d,"method":"%S","params":%s})", id, method, params);
mapQueue->try_emplace(id, response); {
QMetaObject::invokeMethod(&webSocket, std::bind(&QWebSocket::sendTextMessage, &webSocket, S(FormatString(LR"({"id":%d,"method":"%S","params":%s})", id, method, params)))); std::scoped_lock lock(devToolsMutex);
if (webSocket.state() != QAbstractSocket::ConnectedState) return {};
mapQueue.try_emplace(id, response);
webSocket.sendTextMessage(S(message));
webSocket.flush();
}
try { if (auto result = create_task(response).get()[L"result"]) return result; } catch (...) {} try { if (auto result = create_task(response).get()[L"result"]) return result; } catch (...) {}
return {}; return {};
} }

View File

@ -3,7 +3,7 @@
namespace DevTools namespace DevTools
{ {
void Initialize(); void Start(const std::wstring& path, std::function<void(QString)> statusChanged, bool headless);
void Close(); void Close();
bool Connected(); bool Connected();
JSON::Value<wchar_t> SendRequest(const char* method, const std::wstring& params = L"{}"); JSON::Value<wchar_t> SendRequest(const char* method, const std::wstring& params = L"{}");

View File

@ -1,104 +1,42 @@
#include "qtcommon.h" #include "qtcommon.h"
#include "translatewrapper.h"
#include "devtools.h" #include "devtools.h"
#include <QFileDialog>
#include <QMouseEvent>
#include <ShlObj.h>
extern const wchar_t* ERROR_START_CHROME;
extern const wchar_t* TRANSLATION_ERROR; 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 Synchronized<std::wstring> translateTo, translateFrom;
extern QFormLayout* display;
extern Settings settings;
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;
bool translateSelectedOnly = true, rateLimitAll = false, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 2500;
extern const QStringList languagesTo QStringList languages
{ {
"Bulgarian", "Chinese: zh",
"Chinese (Simplified)", "Dutch: nl",
"Czech", "English: en",
"Danish", "French: fr",
"Dutch", "German: de",
"English (American)", "Italian: it",
"English (British)", "Japanese: ja",
"Estonian", "Polish: pl",
"Finnish", "Portuguese: pt",
"French", "Russian: ru",
"German", "Spanish: es",
"Greek",
"Hungarian",
"Italian",
"Japanese",
"Latvian",
"Lithuanian",
"Polish",
"Portuguese",
"Portuguese (Brazilian)",
"Romanian",
"Russian",
"Slovak",
"Slovenian",
"Spanish",
"Swedish"
},
languagesFrom =
{
"Bulgarian",
"Chinese",
"Czech",
"Danish",
"Dutch",
"English",
"Estonian",
"Finnish",
"French",
"German",
"Greek",
"Hungarian",
"Italian",
"Japanese",
"Latvian",
"Lithuanian",
"Polish",
"Portuguese",
"Romanian",
"Russian",
"Slovak",
"Slovenian",
"Spanish",
"Swedish"
}; };
extern const std::unordered_map<std::wstring, std::wstring> codes std::wstring autoDetectLanguage = L"auto";
{
{ { L"Bulgarian" }, { L"Bulgarian" } },
{ { L"Chinese" }, { L"Chinese" } },
{ { L"Chinese (Simplified)" }, { L"Chinese (simplified)" } },
{ { L"Czech" }, { L"Czech" } },
{ { L"Danish" }, { L"Danish" } },
{ { L"Dutch" }, { L"Dutch" } },
{ { L"English" }, { L"English" } },
{ { L"English (American)" }, { L"English (American)" } },
{ { L"English (British)" }, { L"English (British)" } },
{ { L"Estonian" }, { L"Estonian" } },
{ { L"Finnish" }, { L"Finnish" } },
{ { L"French" }, { L"French" } },
{ { L"German" }, { L"German" } },
{ { L"Greek" }, { L"Greek" } },
{ { L"Hungarian" }, { L"Hungarian" } },
{ { L"Italian" }, { L"Italian" } },
{ { L"Japanese" }, { L"Japanese" } },
{ { L"Latvian" }, { L"Latvian" } },
{ { L"Lithuanian" }, { L"Lithuanian" } },
{ { L"Polish" }, { L"Polish" } },
{ { L"Portuguese" }, { L"Portuguese" } },
{ { L"Portuguese (Brazilian)" }, { L"Portuguese (Brazilian)" } },
{ { L"Romanian" }, { L"Romanian" } },
{ { L"Russian" }, { L"Russian" } },
{ { L"Slovak" }, { L"Slovak" } },
{ { L"Slovenian" }, { L"Slovenian" } },
{ { L"Spanish" }, { L"Spanish" } },
{ { L"Swedish" }, { L"Swedish" } },
{ { L"?" }, { L"Detect language" } }
};
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500;
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{ {
@ -106,7 +44,78 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
{ {
case DLL_PROCESS_ATTACH: case DLL_PROCESS_ATTACH:
{ {
DevTools::Initialize(); 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<QMouseEvent*>(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);
} }
break; break;
case DLL_PROCESS_DETACH: case DLL_PROCESS_DETACH:
@ -118,24 +127,19 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
return TRUE; return TRUE;
} }
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp) std::pair<bool, std::wstring> Translate(const std::wstring& text)
{ {
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) };
// DevTools can't handle concurrent translations yet // DevTools can't handle concurrent translations yet
static std::mutex translationMutex; static std::mutex translationMutex;
std::scoped_lock lock(translationMutex); std::scoped_lock lock(translationMutex);
std::wstring escaped; // DeepL breaks with slash in input DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://www.deepl.com/translator#any/%s/%s"})", translateTo.Copy(), Escape(text)));
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)));
for (int retry = 0; ++retry < 20; Sleep(100))
if (Copy(DevTools::SendRequest("Runtime.evaluate", LR"({"expression":"document.readyState"})")[L"result"][L"value"].String()) == L"complete") break;
DevTools::SendRequest("Runtime.evaluate", FormatString(LR"({"expression":" if (translateFrom.Copy() != autoDetectLanguage)
document.querySelector('.lmt__language_select--source').querySelector('button').click(); DevTools::SendRequest("Runtime.evaluate", FormatString(LR"({"expression":"
document.evaluate(`//*[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click(); document.querySelector('.lmt__language_select--source').querySelector('button').click(),
document.querySelector('.lmt__language_select--target').querySelector('button').click(); document.evaluate(`//button[contains(text(),'%s')]`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click()
document.evaluate(`//*[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click(); "})", S(std::find_if(languages.begin(), languages.end(), [end = S(translateFrom.Copy())](const QString& language) { return language.endsWith(end); })->split(":")[0])));
"})", 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",

View File

@ -1,82 +0,0 @@
#include "qtcommon.h"
#include "translatewrapper.h"
#include "devtools.h"
extern const wchar_t* ERROR_START_CHROME;
extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "DevTools Papago Translate";
const char* GET_API_KEY_FROM = nullptr;
extern const QStringList languagesTo
{
"Chinese (Simplified)",
"Chinese (Traditional)",
"English",
"French",
"German",
"Hindi",
"Indonesian",
"Italian",
"Japanese",
"Korean",
"Portuguese",
"Russian",
"Spanish",
"Thai",
"Vietnamese",
}, languagesFrom = languagesTo;
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Chinese (Simplified)" }, { L"zh-CN" } },
{ { L"Chinese (Traditional)" }, { L"zt-TW" } },
{ { L"English" }, { L"en" } },
{ { L"French" }, { L"fr" } },
{ { L"German" }, { L"de" } },
{ { L"Hindi" }, { L"hi" } },
{ { L"Indonesian" }, { L"id" } },
{ { L"Italian" }, { L"it" } },
{ { L"Japanese" }, { L"ja" } },
{ { L"Korean" }, { L"ko" } },
{ { L"Portuguese" }, { L"pt" } },
{ { L"Russian" }, { L"ru" } },
{ { L"Spanish" }, { L"es" } },
{ { L"Thai" }, { L"th" } },
{ { L"Vietnamese" }, { L"vi" } },
{ { L"?" }, { L"auto" } }
};
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500;
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DevTools::Initialize();
}
break;
case DLL_PROCESS_DETACH:
{
DevTools::Close();
}
break;
}
return TRUE;
}
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) };
// DevTools can't handle concurrent translations yet
static std::mutex translationMutex;
std::scoped_lock lock(translationMutex);
DevTools::SendRequest("Page.navigate", FormatString(LR"({"url":"https://papago.naver.com/?sk=%s&tk=%s&st=%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('#txtTarget').textContent.trim() ","returnByValue":true})"
)[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() };
return { false, TRANSLATION_ERROR };
}

View File

@ -1,152 +0,0 @@
#include "qtcommon.h"
#include "translatewrapper.h"
#include "devtools.h"
extern const wchar_t* ERROR_START_CHROME;
extern const wchar_t* TRANSLATION_ERROR;
const char* TRANSLATION_PROVIDER = "DevTools Systran Translate";
const char* GET_API_KEY_FROM = nullptr;
extern const QStringList languagesTo
{
"Albanian",
"Arabic",
"Bengali",
"Bulgarian",
"Burmese",
"Catalan",
"Chinese (Simplified)",
"Chinese (Traditional)",
"Croatian",
"Czech",
"Danish",
"Dutch",
"English",
"Estonian",
"Finnish",
"French",
"German",
"Greek",
"Hebrew",
"Hindi",
"Hungarian",
"Indonesian",
"Italian",
"Japanese",
"Korean",
"Latvian",
"Lithuanian",
"Malay",
"Norwegian",
"Pashto",
"Persian",
"Polish",
"Portuguese",
"Romanian",
"Russian",
"Serbian",
"Slovak",
"Slovenian",
"Somali",
"Spanish",
"Swedish",
"Tagalog",
"Tamil",
"Thai",
"Turkish",
"Ukrainian",
"Urdu",
"Vietnamese"
}, languagesFrom = languagesTo;
extern const std::unordered_map<std::wstring, std::wstring> codes
{
{ { L"Albanian" }, { L"sq" } },
{ { L"Arabic" }, { L"ar" } },
{ { L"Bengali" }, { L"bn" } },
{ { L"Bulgarian" }, { L"bg" } },
{ { L"Burmese" }, { L"my" } },
{ { L"Catalan" }, { L"ca" } },
{ { L"Chinese (Simplified)" }, { L"zh" } },
{ { L"Chinese (Traditional)" }, { L"zt" } },
{ { L"Croatian" }, { L"hr" } },
{ { L"Czech" }, { L"cs" } },
{ { L"Danish" }, { L"da" } },
{ { L"Dutch" }, { L"nl" } },
{ { L"English" }, { L"en" } },
{ { L"Estonian" }, { L"et" } },
{ { L"Finnish" }, { L"fi" } },
{ { L"French" }, { L"fr" } },
{ { L"German" }, { L"de" } },
{ { L"Greek" }, { L"el" } },
{ { L"Hebrew" }, { L"he" } },
{ { L"Hindi" }, { L"hi" } },
{ { L"Hungarian" }, { L"hu" } },
{ { L"Indonesian" }, { L"id" } },
{ { L"Italian" }, { L"it" } },
{ { L"Japanese" }, { L"ja" } },
{ { L"Korean" }, { L"ko" } },
{ { L"Latvian" }, { L"lv" } },
{ { L"Lithuanian" }, { L"lt" } },
{ { L"Malay" }, { L"ms" } },
{ { L"Norwegian" }, { L"no" } },
{ { L"Pashto" }, { L"ps" } },
{ { L"Persian" }, { L"fa" } },
{ { L"Polish" }, { L"pl" } },
{ { L"Portuguese" }, { L"pt" } },
{ { L"Romanian" }, { L"ro" } },
{ { L"Russian" }, { L"ru" } },
{ { L"Serbian" }, { L"sr" } },
{ { L"Slovak" }, { L"sk" } },
{ { L"Slovenian" }, { L"sl" } },
{ { L"Somali" }, { L"so" } },
{ { L"Spanish" }, { L"es" } },
{ { L"Swedish" }, { L"sv" } },
{ { L"Tagalog" }, { L"tl" } },
{ { L"Tamil" }, { L"ta" } },
{ { L"Thai" }, { L"th" } },
{ { L"Turkish" }, { L"tr" } },
{ { L"Ukrainian" }, { L"uk" } },
{ { L"Urdu" }, { L"ur" } },
{ { L"Vietnamese" }, { L"vi" } },
{ { L"?" }, { L"autodetect" } }
};
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 2500;
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DevTools::Initialize();
}
break;
case DLL_PROCESS_DETACH:
{
DevTools::Close();
}
break;
}
return TRUE;
}
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) };
// DevTools can't handle concurrent translations yet
static std::mutex translationMutex;
std::scoped_lock lock(translationMutex);
DevTools::SendRequest(
"Page.navigate",
FormatString(LR"({"url":"https://translate.systran.net/?source=%s&target=%s&input=%s"})", 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('#outputEditor').textContent.trim() ","returnByValue":true})"
)[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() };
return { false, TRANSLATION_ERROR };
}

View File

@ -13,20 +13,15 @@
#include <QFontMetrics> #include <QFontMetrics>
#include <QMouseEvent> #include <QMouseEvent>
#include <QWheelEvent> #include <QWheelEvent>
#include <QScrollArea>
#include <QAbstractNativeEventFilter>
extern const char* EXTRA_WINDOW_INFO; extern const char* EXTRA_WINDOW_INFO;
extern const char* SENTENCE_TOO_BIG;
extern const char* MAX_SENTENCE_SIZE;
extern const char* TOPMOST; extern const char* TOPMOST;
extern const char* OPACITY; extern const char* OPACITY;
extern const char* SHOW_ORIGINAL; extern const char* SHOW_ORIGINAL;
extern const char* ORIGINAL_AFTER_TRANSLATION; extern const char* SHOW_ORIGINAL_INFO;
extern const char* SIZE_LOCK; extern const char* SIZE_LOCK;
extern const char* POSITION_LOCK;
extern const char* CENTERED_TEXT;
extern const char* AUTO_RESIZE_WINDOW_HEIGHT;
extern const char* CLICK_THROUGH;
extern const char* HIDE_MOUSEOVER;
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 +31,9 @@ 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* SAVE_SETTINGS;
constexpr auto DICTIONARY_SAVE_FILE = u8"SavedDictionary.txt"; constexpr auto DICTIONARY_SAVE_FILE = u8"SavedDictionary.txt";
constexpr int CLICK_THROUGH_HOTKEY = 0xc0d0;
QColor colorPrompt(QWidget* parent, QColor default, const QString& title, bool customOpacity = true) QColor colorPrompt(QWidget* parent, QColor default, const QString& title, bool customOpacity = true)
{ {
@ -63,19 +58,13 @@ struct PrettyWindow : QDialog, Localizer
SetTextColor(settings.value(TEXT_COLOR, TextColor()).value<QColor>()); SetTextColor(settings.value(TEXT_COLOR, TextColor()).value<QColor>());
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();
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] { SetBackgroundColor(colorPrompt(this, backgroundColor, BG_COLOR)); });
menu.addAction(TEXT_COLOR, [this] { SetTextColor(colorPrompt(this, TextColor(), TEXT_COLOR)); }); menu.addAction(TEXT_COLOR, [this] { 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); }); connect(ui.display, &QLabel::customContextMenuRequested, [this](QPoint point) { menu.exec(mapToGlobal(point)); });
autoHideAction->setCheckable(true);
autoHideAction->setChecked(autoHide);
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)); });
startTimer(50);
} }
~PrettyWindow() ~PrettyWindow()
@ -86,31 +75,6 @@ struct PrettyWindow : QDialog, Localizer
Ui::ExtraWindow ui; Ui::ExtraWindow ui;
protected: protected:
void timerEvent(QTimerEvent*) override
{
if (autoHide && geometry().contains(QCursor::pos()))
{
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;
repaint();
}
}
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;
repaint();
}
}
QMenu menu{ ui.display }; QMenu menu{ ui.display };
Settings settings{ this }; Settings settings{ this };
@ -151,9 +115,9 @@ private:
{ {
QColor color = colorPrompt(this, outliner->color, OUTLINE_COLOR); QColor color = colorPrompt(this, outliner->color, OUTLINE_COLOR);
if (color.isValid()) outliner->color = color; if (color.isValid()) outliner->color = color;
outliner->size = QInputDialog::getDouble(this, OUTLINE_SIZE, OUTLINE_SIZE_INFO, -outliner->size, 0, INT_MAX, 2, nullptr, Qt::WindowCloseButtonHint); outliner->size = QInputDialog::getDouble(this, OUTLINE_SIZE, OUTLINE_SIZE_INFO, 0.5, 0, INT_MAX, 2, nullptr, Qt::WindowCloseButtonHint);
} }
else outliner->size = -outliner->size; else outliner->size = -1;
settings.setValue(OUTLINE_COLOR, outliner->color.name(QColor::HexArgb)); settings.setValue(OUTLINE_COLOR, outliner->color.name(QColor::HexArgb));
settings.setValue(OUTLINE_SIZE, outliner->size); settings.setValue(OUTLINE_SIZE, outliner->size);
} }
@ -163,7 +127,6 @@ private:
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
{ {
@ -184,26 +147,23 @@ private:
painter->drawPixmap(offset, pixmap); painter->drawPixmap(offset, pixmap);
} }
QColor color{ Qt::black }; QColor color{ Qt::black };
double size = -0.5; double size = -1;
}* outliner; }* outliner;
}; };
class ExtraWindow : public PrettyWindow, QAbstractNativeEventFilter class ExtraWindow : public PrettyWindow
{ {
public: public:
ExtraWindow() : PrettyWindow("Extra Window") ExtraWindow() : PrettyWindow("Extra Window")
{ {
ui.display->setTextFormat(Qt::PlainText); ui.display->setTextFormat(Qt::PlainText);
if (settings.contains(WINDOW) && QApplication::screenAt(settings.value(WINDOW).toRect().bottomRight())) setGeometry(settings.value(WINDOW).toRect()); if (settings.contains(WINDOW) && QApplication::screenAt(settings.value(WINDOW).toRect().bottomRight())) setGeometry(settings.value(WINDOW).toRect());
maxSentenceSize = settings.value(MAX_SENTENCE_SIZE, maxSentenceSize).toInt();
for (auto [name, default, slot] : Array<const char*, bool, void(ExtraWindow::*)(bool)>{ for (auto [name, default, slot] : Array<const char*, bool, void(ExtraWindow::*)(bool)>{
{ TOPMOST, false, &ExtraWindow::SetTopmost }, { TOPMOST, false, &ExtraWindow::SetTopmost },
{ SIZE_LOCK, false, &ExtraWindow::SetSizeLock }, { SIZE_LOCK, false, &ExtraWindow::SetLock },
{ POSITION_LOCK, false, &ExtraWindow::SetPositionLock },
{ CENTERED_TEXT, false, &ExtraWindow::SetCenteredText },
{ AUTO_RESIZE_WINDOW_HEIGHT, false, &ExtraWindow::SetAutoResize },
{ SHOW_ORIGINAL, true, &ExtraWindow::SetShowOriginal }, { SHOW_ORIGINAL, true, &ExtraWindow::SetShowOriginal },
{ ORIGINAL_AFTER_TRANSLATION, true, &ExtraWindow::SetShowOriginalAfterTranslation },
{ DICTIONARY, false, &ExtraWindow::SetUseDictionary }, { DICTIONARY, false, &ExtraWindow::SetUseDictionary },
}) })
{ {
@ -213,15 +173,15 @@ public:
action->setCheckable(true); action->setCheckable(true);
action->setChecked(default); action->setChecked(default);
} }
menu.addAction(MAX_SENTENCE_SIZE, this, [this]
menu.addAction(CLICK_THROUGH, this, &ExtraWindow::ToggleClickThrough); {
settings.setValue(MAX_SENTENCE_SIZE, maxSentenceSize = QInputDialog::getInt(this, MAX_SENTENCE_SIZE, "", maxSentenceSize, 0, INT_MAX, 1, nullptr, Qt::WindowCloseButtonHint));
});
ui.display->installEventFilter(this); ui.display->installEventFilter(this);
qApp->installNativeEventFilter(this); ui.display->setMouseTracking(true);
QMetaObject::invokeMethod(this, [this] QMetaObject::invokeMethod(this, [this]
{ {
RegisterHotKey((HWND)winId(), CLICK_THROUGH_HOTKEY, MOD_ALT | MOD_NOREPEAT, 0x58);
show(); show();
AddSentence(EXTRA_WINDOW_INFO); AddSentence(EXTRA_WINDOW_INFO);
}, Qt::QueuedConnection); }, Qt::QueuedConnection);
@ -234,47 +194,16 @@ public:
void AddSentence(QString sentence) void AddSentence(QString sentence)
{ {
if (sentence.size() > maxSentenceSize) sentence = SENTENCE_TOO_BIG;
if (!showOriginal && sentence.contains(u8"\x200b \n")) sentence = sentence.split(u8"\x200b \n")[1];
sanitize(sentence); sanitize(sentence);
sentence.chop(std::distance(std::remove(sentence.begin(), sentence.end(), QChar::Tabulation), sentence.end())); sentence.chop(std::distance(std::remove(sentence.begin(), sentence.end(), QChar::Tabulation), sentence.end()));
sentenceHistory.push_back(sentence); sentenceHistory.push_back(sentence);
if (sentenceHistory.size() > 1000) sentenceHistory.erase(sentenceHistory.begin());
historyIndex = sentenceHistory.size() - 1; historyIndex = sentenceHistory.size() - 1;
DisplaySentence(); ui.display->setText(sentence);
} }
private: private:
void DisplaySentence()
{
if (sentenceHistory.empty()) return;
QString sentence = sentenceHistory[historyIndex];
if (sentence.contains(u8"\x200b \n"))
if (!showOriginal) sentence = sentence.split(u8"\x200b \n")[1];
else if (showOriginalAfterTranslation) sentence = sentence.split(u8"\x200b \n")[1] + "\n" + sentence.split(u8"\x200b \n")[0];
if (sizeLock && !autoResize)
{
QFontMetrics fontMetrics(ui.display->font(), ui.display);
int low = 0, high = sentence.size(), last = 0;
while (low <= high)
{
int mid = (low + high) / 2;
if (fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence.left(mid)).height() <= ui.display->height())
{
last = mid;
low = mid + 1;
}
else high = mid - 1;
}
sentence = sentence.left(last);
}
ui.display->setText(sentence);
if (autoResize)
resize(width(), height() - ui.display->height() +
QFontMetrics(ui.display->font(), ui.display).boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence).height()
);
}
void SetTopmost(bool topmost) void SetTopmost(bool topmost)
{ {
for (auto window : { winId(), dictionaryWindow.winId() }) for (auto window : { winId(), dictionaryWindow.winId() })
@ -282,39 +211,16 @@ private:
settings.setValue(TOPMOST, topmost); settings.setValue(TOPMOST, topmost);
}; };
void SetPositionLock(bool locked) void SetLock(bool locked)
{
settings.setValue(POSITION_LOCK, posLock = locked);
};
void SetSizeLock(bool locked)
{ {
setSizeGripEnabled(!locked); setSizeGripEnabled(!locked);
settings.setValue(SIZE_LOCK, sizeLock = locked); settings.setValue(SIZE_LOCK, this->locked = locked);
};
void SetCenteredText(bool centeredText)
{
ui.display->setAlignment(centeredText ? Qt::AlignHCenter : Qt::AlignLeft);
settings.setValue(CENTERED_TEXT, this->centeredText = centeredText);
};
void SetAutoResize(bool autoResize)
{
settings.setValue(AUTO_RESIZE_WINDOW_HEIGHT, this->autoResize = autoResize);
DisplaySentence();
}; };
void SetShowOriginal(bool showOriginal) void SetShowOriginal(bool showOriginal)
{ {
if (!showOriginal && settings.value(SHOW_ORIGINAL, false).toBool()) QMessageBox::information(this, SHOW_ORIGINAL, SHOW_ORIGINAL_INFO);
settings.setValue(SHOW_ORIGINAL, this->showOriginal = showOriginal); settings.setValue(SHOW_ORIGINAL, this->showOriginal = showOriginal);
DisplaySentence();
};
void SetShowOriginalAfterTranslation(bool showOriginalAfterTranslation)
{
settings.setValue(ORIGINAL_AFTER_TRANSLATION, this->showOriginalAfterTranslation = showOriginalAfterTranslation);
DisplaySentence();
}; };
void SetUseDictionary(bool useDictionary) void SetUseDictionary(bool useDictionary)
@ -331,40 +237,27 @@ private:
settings.setValue(DICTIONARY, this->useDictionary = useDictionary); settings.setValue(DICTIONARY, this->useDictionary = useDictionary);
} }
void ToggleClickThrough() void ComputeDictionaryPosition(QPoint mouse)
{
clickThrough = !clickThrough;
for (auto window : { winId(), dictionaryWindow.winId() })
{
unsigned exStyle = GetWindowLongPtrW((HWND)window, GWL_EXSTYLE);
if (clickThrough) exStyle |= WS_EX_TRANSPARENT;
else exStyle &= ~WS_EX_TRANSPARENT;
SetWindowLongPtrW((HWND)window, GWL_EXSTYLE, exStyle);
}
};
void ShowDictionary(QPoint mouse)
{ {
QString sentence = ui.display->text(); QString sentence = ui.display->text();
const QFont& font = ui.display->font(); const QFont& font = ui.display->font();
if (cachedDisplayInfo.CompareExchange(ui.display)) if (cachedDisplayInfo.CompareExchange(ui.display))
{ {
QFontMetrics fontMetrics(font, ui.display); QFontMetrics fontMetrics(font, ui.display);
int flags = Qt::TextWordWrap | (ui.display->alignment() & (Qt::AlignLeft | Qt::AlignHCenter));
textPositionMap.clear(); textPositionMap.clear();
for (int i = 0, height = 0, lineBreak = 0; i < sentence.size(); ++i) for (int i = 0, height = 0, lineBreak = 0; i < sentence.size(); ++i)
{ {
int block = 1; int block = 1;
for (int charHeight = fontMetrics.boundingRect(0, 0, 1, INT_MAX, flags, sentence.mid(i, 1)).height(); for (int charHeight = fontMetrics.boundingRect(0, 0, 1, INT_MAX, Qt::TextWordWrap, sentence.mid(i, 1)).height();
i + block < sentence.size() && fontMetrics.boundingRect(0, 0, 1, INT_MAX, flags, sentence.mid(i, block + 1)).height() < charHeight * 1.5; ++block); i + block < sentence.size() && fontMetrics.boundingRect(0, 0, 1, INT_MAX, Qt::TextWordWrap, sentence.mid(i, block + 1)).height() < charHeight * 1.5; ++block);
auto boundingRect = fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, flags, sentence.left(i + block)); auto boundingRect = fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence.left(i + block));
if (boundingRect.height() > height) if (boundingRect.height() > height)
{ {
height = boundingRect.height(); height = boundingRect.height();
lineBreak = i; lineBreak = i;
} }
textPositionMap.push_back({ textPositionMap.push_back({
fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, flags, sentence.mid(lineBreak, i - lineBreak + 1)).right() + 1, fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, Qt::TextWordWrap, sentence.mid(lineBreak, i - lineBreak + 1)).width(),
height height
}); });
} }
@ -381,27 +274,13 @@ private:
dictionaryWindow.move(ui.display->mapToGlobal(QPoint(x, y - dictionaryWindow.height()))); dictionaryWindow.move(ui.display->mapToGlobal(QPoint(x, y - dictionaryWindow.height())));
} }
bool nativeEventFilter(const QByteArray&, void* message, long* result) override
{
auto msg = (MSG*)message;
if (msg->message == WM_HOTKEY)
if (msg->wParam == CLICK_THROUGH_HOTKEY) return ToggleClickThrough(), true;
return false;
}
bool eventFilter(QObject*, QEvent* event) override bool eventFilter(QObject*, QEvent* event) override
{ {
if (event->type() == QEvent::MouseButtonPress) mousePressEvent((QMouseEvent*)event); if (useDictionary && event->type() == QEvent::MouseMove) ComputeDictionaryPosition(((QMouseEvent*)event)->localPos().toPoint());
if (event->type() == QEvent::MouseButtonPress) dictionaryWindow.hide();
return false; return false;
} }
void timerEvent(QTimerEvent* event) override
{
if (useDictionary && QCursor::pos() != oldPos && (!dictionaryWindow.isVisible() || !dictionaryWindow.geometry().contains(QCursor::pos())))
ShowDictionary(ui.display->mapFromGlobal(QCursor::pos()));
PrettyWindow::timerEvent(event);
}
void mousePressEvent(QMouseEvent* event) override void mousePressEvent(QMouseEvent* event) override
{ {
dictionaryWindow.hide(); dictionaryWindow.hide();
@ -410,19 +289,19 @@ private:
void mouseMoveEvent(QMouseEvent* event) override void mouseMoveEvent(QMouseEvent* event) override
{ {
if (!posLock) move(pos() + event->globalPos() - oldPos); if (!locked) move(pos() + event->globalPos() - oldPos);
oldPos = event->globalPos(); oldPos = event->globalPos();
} }
void wheelEvent(QWheelEvent* event) override void wheelEvent(QWheelEvent* event) override
{ {
int scroll = event->angleDelta().y(); int scroll = event->angleDelta().y();
if (scroll > 0 && historyIndex > 0) --historyIndex; if (scroll > 0 && historyIndex > 0) ui.display->setText(sentenceHistory[--historyIndex]);
if (scroll < 0 && historyIndex + 1 < sentenceHistory.size()) ++historyIndex; if (scroll < 0 && historyIndex + 1 < sentenceHistory.size()) ui.display->setText(sentenceHistory[++historyIndex]);
DisplaySentence();
} }
bool sizeLock, posLock, centeredText, autoResize, showOriginal, showOriginalAfterTranslation, useDictionary, clickThrough; bool locked, showOriginal, useDictionary;
int maxSentenceSize = 1000;
QPoint oldPos; QPoint oldPos;
class class
@ -430,11 +309,10 @@ private:
public: public:
bool CompareExchange(QLabel* display) bool CompareExchange(QLabel* display)
{ {
if (display->text() == text && display->font() == font && display->width() == width && display->alignment() == alignment) return false; if (display->text() == text && display->font() == font && display->width() == width) return false;
text = display->text(); text = display->text();
font = display->font(); font = display->font();
width = display->width(); width = display->width();
alignment = display->alignment();
return true; return true;
} }
@ -442,7 +320,6 @@ private:
QString text; QString text;
QFont font; QFont font;
int width; int width;
Qt::Alignment alignment;
} cachedDisplayInfo; } cachedDisplayInfo;
std::vector<QPoint> textPositionMap; std::vector<QPoint> textPositionMap;

View File

@ -10,9 +10,6 @@
<height>300</height> <height>300</height>
</rect> </rect>
</property> </property>
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<layout class="QHBoxLayout"> <layout class="QHBoxLayout">
<item> <item>
<widget class="QLabel" name="display"> <widget class="QLabel" name="display">

View File

@ -1,250 +1,140 @@
#include "qtcommon.h" #include "qtcommon.h"
#include "translatewrapper.h"
#include "network.h" #include "network.h"
#include <ctime>
extern const wchar_t* TRANSLATION_ERROR; extern const wchar_t* TRANSLATION_ERROR;
extern Synchronized<std::wstring> translateTo, translateFrom, authKey;
const char* TRANSLATION_PROVIDER = "Google Translate"; const char* TRANSLATION_PROVIDER = "Google Translate";
const char* GET_API_KEY_FROM = "https://console.cloud.google.com/marketplace/product/google/translate.googleapis.com"; const char* GET_API_KEY_FROM = "https://codelabs.developers.google.com/codelabs/cloud-translation-intro";
extern const QStringList languagesTo QStringList languages
{ {
"Afrikaans", "Afrikaans: af",
"Albanian", "Albanian: sq",
"Amharic", "Amharic: am",
"Arabic", "Arabic: ar",
"Armenian", "Armenian: hy",
"Azerbaijani", "Azerbaijani: az",
"Basque", "Basque: eu",
"Belarusian", "Belarusian: be",
"Bengali", "Bengali: bn",
"Bosnian", "Bosnian: bs",
"Bulgarian", "Bulgarian: bg",
"Catalan", "Catalan: ca",
"Cebuano", "Cebuano: ceb",
"Chichewa", "Chichewa: ny",
"Chinese (Simplified)", "Chinese (simplified): zh",
"Chinese (Traditional)", "Chinese (traditional): zh-TW",
"Corsican", "Corsican: co",
"Croatian", "Croatian: hr",
"Czech", "Czech: cs",
"Danish", "Danish: da",
"Dutch", "Dutch: nl",
"English", "English: en",
"Esperanto", "Esperanto: eo",
"Estonian", "Estonian: et",
"Filipino", "Filipino: tl",
"Finnish", "Finnish: fi",
"French", "French: fr",
"Frisian", "Frisian: fy",
"Galician", "Galician: gl",
"Georgian", "Georgian: ka",
"German", "German: de",
"Greek", "Greek: el",
"Gujarati", "Gujarati: gu",
"Haitian Creole", "Haitian Creole: ht",
"Hausa", "Hausa: ha",
"Hawaiian", "Hawaiian: haw",
"Hebrew", "Hebrew: iw",
"Hindi", "Hindi: hi",
"Hmong", "Hmong: hmn",
"Hungarian", "Hungarian: hu",
"Icelandic", "Icelandic: is",
"Igbo", "Igbo: ig",
"Indonesian", "Indonesian: id",
"Irish", "Irish: ga",
"Italian", "Italian: it",
"Japanese", "Japanese: ja",
"Javanese", "Javanese: jw",
"Kannada", "Kannada: kn",
"Kazakh", "Kazakh: kk",
"Khmer", "Khmer: km",
"Kinyarwanda", "Kinyarwanda: rw",
"Korean", "Korean: ko",
"Kurdish (Kurmanji)", "Kurdish (Kurmanji): ku",
"Kyrgyz", "Kyrgyz: ky",
"Lao", "Lao: lo",
"Latin", "Latin: la",
"Latvian", "Latvian: lv",
"Lithuanian", "Lithuanian: lt",
"Luxembourgish", "Luxembourgish: lb",
"Macedonian", "Macedonian: mk",
"Malagasy", "Malagasy: mg",
"Malay", "Malay: ms",
"Malayalam", "Malayalam: ml",
"Maltese", "Maltese: mt",
"Maori", "Maori: mi",
"Marathi", "Marathi: mr",
"Mongolian", "Mongolian: mn",
"Myanmar (Burmese)", "Myanmar (Burmese): my",
"Nepali", "Nepali: ne",
"Norwegian", "Norwegian: no",
"Odia (Oriya)", "Odia (Oriya): or",
"Pashto", "Pashto: ps",
"Persian", "Persian: fa",
"Polish", "Polish: pl",
"Portuguese", "Portuguese: pt",
"Punjabi", "Punjabi: pa",
"Romanian", "Romanian: ro",
"Russian", "Russian: ru",
"Samoan", "Samoan: sm",
"Scots Gaelic", "Scots Gaelic: gd",
"Serbian", "Serbian: sr",
"Sesotho", "Sesotho: st",
"Shona", "Shona: sn",
"Sindhi", "Sindhi: sd",
"Sinhala", "Sinhala: si",
"Slovak", "Slovak: sk",
"Slovenian", "Slovenian: sl",
"Somali", "Somali: so",
"Spanish", "Spanish: es",
"Sundanese", "Sundanese: su",
"Swahili", "Swahili: sw",
"Swedish", "Swedish: sv",
"Tajik", "Tajik: tg",
"Tamil", "Tamil: ta",
"Tatar", "Tatar: tt",
"Telugu", "Telugu: te",
"Thai", "Thai: th",
"Turkish", "Turkish: tr",
"Turkmen", "Turkmen: tk",
"Ukrainian", "Ukrainian: uk",
"Urdu", "Urdu: ur",
"Uyghur", "Uyghur: ug",
"Uzbek", "Uzbek: uz",
"Vietnamese", "Vietnamese: vi",
"Welsh", "Welsh: cy",
"Xhosa", "Xhosa: xh",
"Yiddish", "Yiddish: yi",
"Yoruba", "Yoruba: yo",
"Zulu", "Zulu: zu"
}, 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"Basque" }, { L"eu" } },
{ { L"Belarusian" }, { L"be" } },
{ { L"Bengali" }, { L"bn" } },
{ { L"Bosnian" }, { L"bs" } },
{ { L"Bulgarian" }, { L"bg" } },
{ { L"Catalan" }, { L"ca" } },
{ { L"Cebuano" }, { L"ceb" } },
{ { L"Chichewa" }, { L"ny" } },
{ { L"Chinese (Simplified)" }, { L"zh-CN" } },
{ { L"Chinese (Traditional)" }, { L"zh-TW" } },
{ { L"Corsican" }, { L"co" } },
{ { L"Croatian" }, { L"hr" } },
{ { L"Czech" }, { L"cs" } },
{ { L"Danish" }, { L"da" } },
{ { L"Dutch" }, { L"nl" } },
{ { L"English" }, { L"en" } },
{ { L"Esperanto" }, { L"eo" } },
{ { L"Estonian" }, { L"et" } },
{ { L"Filipino" }, { L"tl" } },
{ { L"Finnish" }, { L"fi" } },
{ { L"French" }, { L"fr" } },
{ { L"Frisian" }, { L"fy" } },
{ { L"Galician" }, { L"gl" } },
{ { L"Georgian" }, { L"ka" } },
{ { L"German" }, { L"de" } },
{ { L"Greek" }, { L"el" } },
{ { L"Gujarati" }, { L"gu" } },
{ { L"Haitian Creole" }, { L"ht" } },
{ { L"Hausa" }, { L"ha" } },
{ { L"Hawaiian" }, { L"haw" } },
{ { L"Hebrew" }, { L"iw" } },
{ { L"Hindi" }, { L"hi" } },
{ { L"Hmong" }, { L"hmn" } },
{ { L"Hungarian" }, { L"hu" } },
{ { L"Icelandic" }, { L"is" } },
{ { L"Igbo" }, { L"ig" } },
{ { L"Indonesian" }, { L"id" } },
{ { L"Irish" }, { L"ga" } },
{ { L"Italian" }, { L"it" } },
{ { L"Japanese" }, { L"ja" } },
{ { L"Javanese" }, { L"jw" } },
{ { L"Kannada" }, { L"kn" } },
{ { L"Kazakh" }, { L"kk" } },
{ { L"Khmer" }, { L"km" } },
{ { L"Kinyarwanda" }, { L"rw" } },
{ { L"Korean" }, { L"ko" } },
{ { L"Kurdish (Kurmanji)" }, { L"ku" } },
{ { 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"Mongolian" }, { L"mn" } },
{ { L"Myanmar (Burmese)" }, { L"my" } },
{ { L"Nepali" }, { L"ne" } },
{ { L"Norwegian" }, { L"no" } },
{ { L"Odia (Oriya)" }, { L"or" } },
{ { L"Pashto" }, { L"ps" } },
{ { 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"Samoan" }, { L"sm" } },
{ { L"Scots Gaelic" }, { L"gd" } },
{ { L"Serbian" }, { L"sr" } },
{ { L"Sesotho" }, { L"st" } },
{ { L"Shona" }, { L"sn" } },
{ { L"Sindhi" }, { L"sd" } },
{ { L"Sinhala" }, { L"si" } },
{ { L"Slovak" }, { L"sk" } },
{ { L"Slovenian" }, { L"sl" } },
{ { L"Somali" }, { L"so" } },
{ { L"Spanish" }, { L"es" } },
{ { L"Sundanese" }, { L"su" } },
{ { L"Swahili" }, { L"sw" } },
{ { L"Swedish" }, { L"sv" } },
{ { 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"Turkmen" }, { L"tk" } },
{ { L"Ukrainian" }, { L"uk" } },
{ { L"Urdu" }, { L"ur" } },
{ { L"Uyghur" }, { L"ug" } },
{ { L"Uzbek" }, { L"uz" } },
{ { L"Vietnamese" }, { L"vi" } },
{ { L"Welsh" }, { L"cy" } },
{ { L"Xhosa" }, { L"xh" } },
{ { L"Yiddish" }, { L"yi" } },
{ { L"Yoruba" }, { L"yo" } },
{ { L"Zulu" }, { L"zu" } },
{ { L"?" }, { L"auto" } }
}; };
std::wstring autoDetectLanguage = L"auto";
bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true; bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000; int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 1000;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp) std::pair<bool, std::wstring> Translate(const std::wstring& text)
{ {
if (!tlp.authKey.empty()) if (!authKey->empty())
{ {
std::wstring translateFromComponent = tlp.translateFrom == L"?" ? L"" : L"&source=" + codes.at(tlp.translateFrom); std::wstring translateFromComponent = translateFrom.Copy() == autoDetectLanguage ? L"" : L"&source=" + translateFrom.Copy();
if (HttpRequest httpRequest{ if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor", L"Mozilla/5.0 Textractor",
L"translation.googleapis.com", L"translation.googleapis.com",
L"POST", L"POST",
FormatString(L"/language/translate/v2?format=text&target=%s&key=%s%s", codes.at(tlp.translateTo), tlp.authKey, translateFromComponent).c_str(), FormatString(L"/language/translate/v2?format=text&target=%s&key=%s%s", translateTo.Copy(), authKey.Copy(), translateFromComponent).c_str(),
FormatString(R"({"q":["%s"]})", JSON::Escape(WideStringToString(text))) FormatString(R"({"q":["%s"]})", JSON::Escape(WideStringToString(text)))
}) })
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"data"][L"translations"][0][L"translatedText"].String())) return { true, translation.value() }; if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"data"][L"translations"][0][L"translatedText"].String())) return { true, translation.value() };
@ -256,11 +146,11 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationPar
L"Mozilla/5.0 Textractor", L"Mozilla/5.0 Textractor",
L"translate.google.com", L"translate.google.com",
L"GET", L"GET",
FormatString(L"/m?sl=%s&tl=%s&q=%s", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text)).c_str() FormatString(L"/m?sl=%s&tl=%s&q=%s", translateFrom.Copy(), translateTo.Copy(), Escape(text)).c_str()
}) })
{ {
auto start = httpRequest.response.find(L"result-container\">"), end = httpRequest.response.find(L'<', start); auto start = httpRequest.response.find(L"result-container\">") + 18, end = httpRequest.response.find(L'<', start);
if (end != std::string::npos) return { true, HTML::Unescape(httpRequest.response.substr(start + 18, end - start - 18)) }; if (start != end) return { true, HTML::Unescape(httpRequest.response.substr(start, end - start)) };
return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) }; return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
} }
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) }; else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };

View File

@ -0,0 +1,47 @@
#include "qtcommon.h"
#include "network.h"
extern const wchar_t* TRANSLATION_ERROR;
extern Synchronized<std::wstring> translateTo, translateFrom;
const char* TRANSLATION_PROVIDER = "Libre Translate";
const char* GET_API_KEY_FROM = nullptr;
QStringList languages
{
"Arabic: ar",
"Chinese: zh",
"English: en",
"French: fr",
"German: de",
"Hindi: hi",
"Irish: ga",
"Italian: it",
"Japanese: ja",
"Korean: ko",
"Portuguese: pt",
"Russian: ru",
"Spanish: es"
};
std::wstring autoDetectLanguage = L"";
bool translateSelectedOnly = true, rateLimitAll = true, rateLimitSelected = false, useCache = true, useFilter = true;
int tokenCount = 10, tokenRestoreDelay = 60000, maxSentenceSize = 10000;
std::pair<bool, std::wstring> Translate(const std::wstring& text)
{
if (HttpRequest httpRequest{
L"Mozilla/5.0 Textractor",
L"127.0.0.1",
L"POST",
L"/translate",
FormatString("q=%S&source=%S&target=%S", Escape(text), translateFrom.Copy(), translateTo.Copy()),
NULL,
5000,
NULL,
0
})
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"translatedText"].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

@ -12,9 +12,9 @@ extern const char* CURRENT_FILTER;
const char* REGEX_SAVE_FILE = "SavedRegexFilters.txt"; const char* REGEX_SAVE_FILE = "SavedRegexFilters.txt";
std::optional<std::wregex> regex; std::optional<std::wregex> regex;
std::wstring replace = L"$1"; std::wstring replace;
concurrency::reader_writer_lock m; std::shared_mutex m;
DWORD (*GetSelectedProcessId)() = [] { return 0UL; }; DWORD (*GetSelectedProcessId)() = nullptr;
class Window : public QDialog, Localizer class Window : public QDialog, Localizer
{ {
@ -56,7 +56,7 @@ private:
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo) bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{ {
static auto _ = GetSelectedProcessId = (DWORD(*)())sentenceInfo["get selected process id"]; static auto _ = GetSelectedProcessId = (DWORD(*)())sentenceInfo["DWORD (*GetSelectedProcessId)()"];
if (sentenceInfo["text number"] == 0) return false; if (sentenceInfo["text number"] == 0) return false;
if (/*sentenceInfo["current select"] && */!regex) if (auto processName = GetModuleFilename(sentenceInfo["process id"])) if (/*sentenceInfo["current select"] && */!regex) if (auto processName = GetModuleFilename(sentenceInfo["process id"]))
{ {
@ -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))); 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); if (!regexes.empty()) QMetaObject::invokeMethod(&window, std::bind(&Window::SetRegex, &window, S(regexes.back())), Qt::BlockingQueuedConnection);
} }
concurrency::reader_writer_lock::scoped_lock_read readLock(m); std::shared_lock lock(m);
if (regex) sentence = std::regex_replace(sentence, regex.value(), replace); if (regex) sentence = std::regex_replace(sentence, regex.value(), replace);
return true; return true;
} }

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>500</width> <width>500</width>
<height>80</height> <height>107</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout"> <layout class="QVBoxLayout">
@ -16,13 +16,6 @@
<item> <item>
<widget class="QLineEdit" name="regexEdit"/> <widget class="QLineEdit" name="regexEdit"/>
</item> </item>
<item>
<widget class="QPushButton" name="saveButton">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -35,6 +28,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="saveButton">
<property name="text">
<string>Save</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QLabel"> <widget class="QLabel">
<property name="text"> <property name="text">

View File

@ -1,72 +0,0 @@
#include "extension.h"
#include "module.h"
#include "blockmarkup.h"
#include <fstream>
#include <process.h>
extern const wchar_t* REGEX_REPLACER_INSTRUCTIONS;
const char* REPLACE_SAVE_FILE = "SavedRegexReplacements.txt";
std::atomic<std::filesystem::file_time_type> replaceFileLastWrite = {};
concurrency::reader_writer_lock m;
std::vector<std::tuple<std::wregex, std::wstring, std::regex_constants::match_flag_type>> replacements;
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);
replacements.clear();
std::ifstream stream(REPLACE_SAVE_FILE, std::ios::binary);
BlockMarkupIterator savedFilters(stream, Array<std::wstring_view>{ L"|REGEX|", L"|BECOMES|", L"|MODIFIER|" });
while (auto read = savedFilters.Next())
{
const auto& [regex, replacement, modifier] = read.value();
try
{
replacements.emplace_back(
std::wregex(regex, modifier.find(L'i') == std::string::npos ? std::regex::ECMAScript : std::regex::icase),
replacement,
modifier.find(L'g') == std::string::npos ? std::regex_constants::format_first_only : std::regex_constants::format_default
);
}
catch (std::regex_error) {}
}
}
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 (replacements.empty())
{
auto file = std::ofstream(REPLACE_SAVE_FILE, std::ios::binary) << "\xff\xfe";
for (auto ch : std::wstring_view(REGEX_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)
{
UpdateReplacements();
concurrency::reader_writer_lock::scoped_lock_read readLock(m);
for (const auto& [regex, replacement, flags] : replacements) sentence = std::regex_replace(sentence, regex, replacement, flags);
return true;
}

View File

@ -5,25 +5,30 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
if (sentenceInfo["text number"] == 0) return false; if (sentenceInfo["text number"] == 0) return false;
std::vector<int> repeatNumbers(sentence.size() + 1, 0); std::vector<int> repeatNumbers(sentence.size() + 1, 0);
for (int i = 0; i < sentence.size(); ++i) int repeatNumber = 1;
wchar_t prevChar = 0;
for (auto nextChar : sentence)
{ {
if (sentence[i] != sentence[i + 1]) if (nextChar == prevChar)
{ {
int j = i; repeatNumber += 1;
while (sentence[j] == sentence[i] && --j >= 0); }
repeatNumbers[i - j] += 1; else
{
prevChar = nextChar;
repeatNumbers.at(repeatNumber) += 1;
repeatNumber = 1;
} }
} }
int repeatNumber = std::distance(repeatNumbers.begin(), std::max_element(repeatNumbers.rbegin(), repeatNumbers.rend()).base() - 1); if ((repeatNumber = std::distance(repeatNumbers.begin(), std::max_element(repeatNumbers.begin(), repeatNumbers.end()))) == 1) return false;
if (repeatNumber < 2) return false;
std::wstring newSentence; std::wstring newSentence;
for (int i = 0; i < sentence.size();) for (int i = 0; i < sentence.size();)
{ {
newSentence.push_back(sentence[i]); newSentence.push_back(sentence.at(i));
for (int j = i; j <= sentence.size(); ++j) for (int j = i; j <= sentence.size(); ++j)
{ {
if (j == sentence.size() || sentence[i] != sentence[j]) if (j == sentence.size() || sentence.at(i) != sentence.at(j))
{ {
i += (j - i) % repeatNumber == 0 ? repeatNumber : 1; i += (j - i) % repeatNumber == 0 ? repeatNumber : 1;
break; break;
@ -39,11 +44,8 @@ TEST(
InfoForExtension nonConsole[] = { { "text number", 1 }, {} }; InfoForExtension nonConsole[] = { { "text number", 1 }, {} };
std::wstring repeatedChars = L"aaaaaaaaaaaabbbbbbcccdddaabbbcccddd"; std::wstring repeatedChars = L"aaaaaaaaaaaabbbbbbcccdddaabbbcccddd";
std::wstring someRepeatedChars = L"abcdefaabbccddeeff";
ProcessSentence(repeatedChars, { nonConsole }); ProcessSentence(repeatedChars, { nonConsole });
ProcessSentence(someRepeatedChars, { nonConsole });
assert(repeatedChars.find(L"aaaabbcd") == 0); assert(repeatedChars.find(L"aaaabbcd") == 0);
assert(someRepeatedChars == L"abcdefabcdef");
std::wstring empty = L"", one = L" ", normal = L"This is a normal sentence. はい"; std::wstring empty = L"", one = L" ", normal = L"This is a normal sentence. はい";
ProcessSentence(empty, { nonConsole }); ProcessSentence(empty, { nonConsole });

View File

@ -16,10 +16,10 @@ std::vector<int> GenerateSuffixArray(const std::wstring& text)
eqClasses[suffixArray[0]] = 0; eqClasses[suffixArray[0]] = 0;
for (int i = 1; i < text.size(); ++i) for (int i = 1; i < text.size(); ++i)
{ {
int currentSuffix = suffixArray[i], lastSuffix = suffixArray[i - 1]; int currentSuffix = suffixArray[i];
int lastSuffix = suffixArray[i - 1];
if (currentSuffix + length < text.size() && prevEqClasses[currentSuffix] == prevEqClasses[lastSuffix] && if (currentSuffix + length < text.size() && prevEqClasses[currentSuffix] == prevEqClasses[lastSuffix] &&
prevEqClasses[currentSuffix + length / 2] == prevEqClasses[lastSuffix + length / 2] prevEqClasses[currentSuffix + length / 2] == prevEqClasses.at(lastSuffix + length / 2)) // not completely certain that this will stay in range
)
eqClasses[currentSuffix] = eqClasses[lastSuffix]; eqClasses[currentSuffix] = eqClasses[lastSuffix];
else eqClasses[currentSuffix] = i; else eqClasses[currentSuffix] = i;
} }

View File

@ -10,7 +10,7 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
{ {
wchar_t filePath[MAX_PATH]; wchar_t filePath[MAX_PATH];
GetModuleFileNameW(hModule, filePath, MAX_PATH); GetModuleFileNameW(hModule, filePath, MAX_PATH);
if (wchar_t* fileName = wcsrchr(filePath, L'\\')) swscanf_s(fileName, L"\\Remove %d Repeated Sentences.xdll", &sentenceCacheSize); if (wchar_t* fileName = wcsrchr(filePath, L'\\')) swscanf_s(fileName, L"\\Remove %d Repeated Sentences.dll", &sentenceCacheSize);
} }
break; break;
case DLL_PROCESS_DETACH: case DLL_PROCESS_DETACH:
@ -30,7 +30,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
static std::mutex m; static std::mutex m;
m.lock(); m.lock();
if (textNumber + 1 > cache.size()) cache.resize(textNumber + 1); if (textNumber + 1 > cache.size()) cache.resize(textNumber + 1);
auto prevSentences = cache[textNumber].Acquire(); auto prevSentences = cache.at(textNumber).Acquire();
m.unlock(); m.unlock();
auto& inserted = prevSentences->emplace_back(sentence); auto& inserted = prevSentences->emplace_back(sentence);
auto firstLocation = std::find(prevSentences->begin(), prevSentences->end(), sentence); auto firstLocation = std::find(prevSentences->begin(), prevSentences->end(), sentence);

View File

@ -10,7 +10,7 @@ extern const wchar_t* REPLACER_INSTRUCTIONS;
constexpr auto REPLACE_SAVE_FILE = u8"SavedReplacements.txt"; constexpr auto REPLACE_SAVE_FILE = u8"SavedReplacements.txt";
std::atomic<std::filesystem::file_time_type> replaceFileLastWrite = {}; std::atomic<std::filesystem::file_time_type> replaceFileLastWrite = {};
concurrency::reader_writer_lock m; std::shared_mutex m;
class Trie class Trie
{ {
@ -105,7 +105,7 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
auto file = std::ofstream(REPLACE_SAVE_FILE, std::ios::binary) << "\xff\xfe"; auto file = std::ofstream(REPLACE_SAVE_FILE, std::ios::binary) << "\xff\xfe";
for (auto ch : std::wstring_view(REPLACER_INSTRUCTIONS)) 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)); 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 _spawnlp(_P_DETACH, "notepad", "notepad", REPLACE_SAVE_FILE, NULL); // show file to user
} }
} }
break; break;
@ -121,7 +121,7 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo)
{ {
UpdateReplacements(); UpdateReplacements();
concurrency::reader_writer_lock::scoped_lock_read readLock(m); std::shared_lock lock(m);
sentence = trie.Replace(sentence); sentence = trie.Replace(sentence);
return true; return true;
} }

View File

@ -4,7 +4,7 @@
extern const char* LOAD_SCRIPT; extern const char* LOAD_SCRIPT;
constexpr auto STYLE_SAVE_FILE = u8"Textractor.qss"; constexpr auto STYLE_SAVE_FILE = u8"Textractor.css";
class Window : public QDialog, Localizer class Window : public QDialog, Localizer
{ {
@ -13,8 +13,7 @@ public:
{ {
connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript); connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript);
if (scriptEditor.toPlainText().isEmpty()) if (scriptEditor.toPlainText().isEmpty()) scriptEditor.setPlainText("/*https://doc.qt.io/qt-5/stylesheet-syntax.html*/");
scriptEditor.setPlainText("/*\nhttps://www.google.com/search?q=Qt+stylesheet+gallery\nhttps://doc.qt.io/qt-5/stylesheet-syntax.html\n*/");
layout.addWidget(&scriptEditor); layout.addWidget(&scriptEditor);
layout.addWidget(&loadButton); layout.addWidget(&loadButton);

View File

@ -1,30 +1,25 @@
#include "qtcommon.h" #include "qtcommon.h"
#include "extension.h" #include "extension.h"
#include "ui_threadlinker.h"
#include <QKeyEvent> #include <QKeyEvent>
extern const char* THREAD_LINKER; extern const char* THREAD_LINKER;
extern const char* LINK; extern const char* LINK;
extern const char* UNLINK;
extern const char* THREAD_LINK_FROM; extern const char* THREAD_LINK_FROM;
extern const char* THREAD_LINK_TO; extern const char* THREAD_LINK_TO;
extern const char* HEXADECIMAL; extern const char* HEXADECIMAL;
std::unordered_map<int64_t, std::unordered_set<int64_t>> links; std::unordered_map<int64_t, std::unordered_multiset<int64_t>> linkedTextHandles;
std::unordered_set<int64_t> universalLinks, empty; std::shared_mutex m;
bool separateSentences = false; // allow user to change?
concurrency::reader_writer_lock m;
class Window : public QDialog, Localizer class Window : public QDialog, Localizer
{ {
public: public:
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint) Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
{ {
ui.setupUi(this); connect(&linkButton, &QPushButton::clicked, this, &Window::Link);
ui.linkButton->setText(LINK);
ui.unlinkButton->setText(UNLINK); layout.addWidget(&linkList);
connect(ui.linkButton, &QPushButton::clicked, this, &Window::Link); layout.addWidget(&linkButton);
connect(ui.unlinkButton, &QPushButton::clicked, this, &Window::Unlink);
setWindowTitle(THREAD_LINKER); setWindowTitle(THREAD_LINKER);
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection); QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
@ -34,46 +29,39 @@ private:
void Link() void Link()
{ {
bool ok1, ok2, ok3, ok4; bool ok1, ok2, ok3, ok4;
QString fromInput = QInputDialog::getText(this, THREAD_LINK_FROM, HEXADECIMAL, QLineEdit::Normal, "All", &ok1, Qt::WindowCloseButtonHint); int from = QInputDialog::getText(this, THREAD_LINK_FROM, HEXADECIMAL, QLineEdit::Normal, "", &ok1, Qt::WindowCloseButtonHint).toInt(&ok2, 16);
int from = fromInput.toInt(&ok2, 16); int to = QInputDialog::getText(this, THREAD_LINK_TO, HEXADECIMAL, QLineEdit::Normal, "", &ok3, Qt::WindowCloseButtonHint).toInt(&ok4, 16);
if (ok1 && (fromInput == "All" || ok2)) if (ok1 && ok2 && ok3 && ok4)
{ {
int to = QInputDialog::getText(this, THREAD_LINK_TO, HEXADECIMAL, QLineEdit::Normal, "", &ok3, Qt::WindowCloseButtonHint).toInt(&ok4, 16);
if (ok3 && ok4)
{
std::scoped_lock lock(m);
if ((ok2 ? links[from] : universalLinks).insert(to).second)
ui.linkList->addItem((ok2 ? QString::number(from, 16) : "All") + "->" + QString::number(to, 16));
}
}
}
void Unlink()
{
if (ui.linkList->currentItem())
{
QStringList link = ui.linkList->currentItem()->text().split("->");
ui.linkList->takeItem(ui.linkList->currentRow());
std::scoped_lock lock(m); std::scoped_lock lock(m);
(link[0] == "All" ? universalLinks : links[link[0].toInt(nullptr, 16)]).erase(link[1].toInt(nullptr, 16)); linkedTextHandles[from].insert(to);
linkList.addItem(QString::number(from, 16) + "->" + QString::number(to, 16));
} }
} }
void keyPressEvent(QKeyEvent* event) override void keyPressEvent(QKeyEvent* event) override
{ {
if (event->key() == Qt::Key_Delete) Unlink(); if (event->key() == Qt::Key_Delete && linkList.currentItem())
{
QStringList link = linkList.currentItem()->text().split("->");
linkList.takeItem(linkList.currentRow());
std::scoped_lock lock(m);
linkedTextHandles[link[0].toInt(nullptr, 16)].erase(link[1].toInt(nullptr, 16));
}
} }
Ui::LinkWindow ui; QHBoxLayout layout{ this };
QListWidget linkList{ this };
QPushButton linkButton{ LINK, this };
} window; } window;
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo) bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{ {
concurrency::reader_writer_lock::scoped_lock_read readLock(m); std::shared_lock lock(m);
auto action = separateSentences ? sentenceInfo["add sentence"] : sentenceInfo["add text"]; int64_t textHandle = sentenceInfo["text number"];
auto it = links.find(sentenceInfo["text number"]);
for (const auto& linkSet : { it != links.end() ? it->second : empty, sentenceInfo["text number"] > 1 ? universalLinks : empty }) for (auto linkedHandle : linkedTextHandles[textHandle])
for (auto link : linkSet) ((void(*)(int64_t, const wchar_t*))sentenceInfo["void (*AddText)(int64_t number, const wchar_t* text)"])(linkedHandle, sentence.c_str());
((void(*)(int64_t, const wchar_t*))action)(link, sentence.c_str());
return false; return false;
} }

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>LinkWindow</class>
<widget class="QDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<layout class="QHBoxLayout">
<item>
<widget class="QListWidget" name="linkList"/>
</item>
<item>
<layout class="QVBoxLayout">
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="linkButton">
</widget>
</item>
<item>
<widget class="QPushButton" name="unlinkButton">
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,8 +1,8 @@
#include "qtcommon.h" #include "qtcommon.h"
#include "extension.h" #include "extension.h"
#include "translatewrapper.h"
#include "blockmarkup.h" #include "blockmarkup.h"
#include <concurrent_priority_queue.h> #include "network.h"
#include <map>
#include <fstream> #include <fstream>
#include <QComboBox> #include <QComboBox>
@ -14,51 +14,39 @@ extern const char* RATE_LIMIT_ALL_THREADS;
extern const char* RATE_LIMIT_SELECTED_THREAD; extern const char* RATE_LIMIT_SELECTED_THREAD;
extern const char* USE_TRANS_CACHE; extern const char* USE_TRANS_CACHE;
extern const char* FILTER_GARBAGE; extern const char* FILTER_GARBAGE;
extern const char* MAX_TRANSLATIONS_IN_TIMESPAN; extern const char* RATE_LIMIT_TOKEN_COUNT;
extern const char* TIMESPAN; extern const char* RATE_LIMIT_TOKEN_RESTORE_DELAY;
extern const char* MAX_SENTENCE_SIZE; extern const char* MAX_SENTENCE_SIZE;
extern const char* API_KEY; extern const char* API_KEY;
extern const wchar_t* SENTENCE_TOO_LARGE_TO_TRANS;
extern const wchar_t* TRANSLATION_ERROR;
extern const wchar_t* TOO_MANY_TRANS_REQUESTS; extern const wchar_t* TOO_MANY_TRANS_REQUESTS;
extern const char* TRANSLATION_PROVIDER; extern const char* TRANSLATION_PROVIDER;
extern const char* GET_API_KEY_FROM; extern const char* GET_API_KEY_FROM;
extern const QStringList languagesTo, languagesFrom; extern QStringList languages;
extern bool translateSelectedOnly, useRateLimiter, rateLimitSelected, useCache, useFilter; extern std::wstring autoDetectLanguage;
extern int tokenCount, rateLimitTimespan, maxSentenceSize; extern bool translateSelectedOnly, rateLimitAll, rateLimitSelected, useCache, useFilter;
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp); extern int tokenCount, tokenRestoreDelay, maxSentenceSize;
std::pair<bool, std::wstring> Translate(const std::wstring& text);
// backwards compatibility
const char* LANGUAGE = u8"Language";
const std::string TRANSLATION_CACHE_FILE = FormatString("%s Translation Cache.txt", TRANSLATION_PROVIDER);
QFormLayout* display; QFormLayout* display;
Settings settings; Settings settings;
Synchronized<std::wstring> translateTo = L"en", translateFrom = L"auto", authKey;
namespace namespace
{ {
Synchronized<TranslationParam> tlp; Synchronized<std::map<std::wstring, std::wstring>> translationCache;
Synchronized<std::unordered_map<std::wstring, std::wstring>> translationCache; int savedSize;
std::string CacheFile()
{
return FormatString("%s Cache (%S).txt", TRANSLATION_PROVIDER, tlp->translateTo);
}
void SaveCache() void SaveCache()
{ {
std::wstring allTranslations(L"\xfeff"); std::wstring allTranslations(L"\xfeff");
for (const auto& [sentence, translation] : translationCache.Acquire().contents) for (const auto& [sentence, translation] : translationCache.Acquire().contents)
allTranslations.append(L"|SENTENCE|").append(sentence).append(L"|TRANSLATION|").append(translation).append(L"|END|\r\n"); allTranslations.append(L"|SENTENCE|").append(sentence).append(L"|TRANSLATION|").append(translation).append(L"|END|\r\n");
std::ofstream(CacheFile(), std::ios::binary | std::ios::trunc).write((const char*)allTranslations.c_str(), allTranslations.size() * sizeof(wchar_t)); std::ofstream(TRANSLATION_CACHE_FILE, std::ios::binary | std::ios::trunc).write((const char*)allTranslations.c_str(), allTranslations.size() * sizeof(wchar_t));
} savedSize = translationCache->size();
void LoadCache()
{
translationCache->clear();
std::ifstream stream(CacheFile(), std::ios::binary);
BlockMarkupIterator savedTranslations(stream, Array<std::wstring_view>{ L"|SENTENCE|", L"|TRANSLATION|" });
auto translationCache = ::translationCache.Acquire();
while (auto read = savedTranslations.Next())
{
auto& [sentence, translation] = read.value();
translationCache->try_emplace(std::move(sentence), std::move(translation));
}
} }
} }
@ -72,28 +60,29 @@ public:
settings.beginGroup(TRANSLATION_PROVIDER); settings.beginGroup(TRANSLATION_PROVIDER);
auto translateToCombo = new QComboBox(this); auto translateToCombo = new QComboBox(this);
translateToCombo->addItems(languagesTo); translateToCombo->addItems(languages);
int i = -1; int language = -1;
if (settings.contains(TRANSLATE_TO)) i = translateToCombo->findText(settings.value(TRANSLATE_TO).toString()); if (settings.contains(LANGUAGE)) language = translateToCombo->findText(settings.value(LANGUAGE).toString(), Qt::MatchEndsWith);
if (i < 0) i = translateToCombo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith); if (settings.contains(TRANSLATE_TO)) language = translateToCombo->findText(settings.value(TRANSLATE_TO).toString(), Qt::MatchEndsWith);
if (i < 0) i = translateToCombo->findText("English", Qt::MatchStartsWith); if (language < 0) language = translateToCombo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith);
translateToCombo->setCurrentIndex(i); if (language < 0) language = translateToCombo->findText("English", Qt::MatchStartsWith);
translateToCombo->setCurrentIndex(language);
SaveTranslateTo(translateToCombo->currentText()); SaveTranslateTo(translateToCombo->currentText());
display->addRow(TRANSLATE_TO, translateToCombo); display->addRow(TRANSLATE_TO, translateToCombo);
connect(translateToCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateTo); connect(translateToCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateTo);
languages.push_front("?: " + S(autoDetectLanguage));
auto translateFromCombo = new QComboBox(this); auto translateFromCombo = new QComboBox(this);
translateFromCombo->addItem("?"); translateFromCombo->addItems(languages);
translateFromCombo->addItems(languagesFrom); language = -1;
i = -1; if (settings.contains(TRANSLATE_FROM)) language = translateFromCombo->findText(settings.value(TRANSLATE_FROM).toString(), Qt::MatchEndsWith);
if (settings.contains(TRANSLATE_FROM)) i = translateFromCombo->findText(settings.value(TRANSLATE_FROM).toString()); if (language < 0) language = translateFromCombo->findText("?", Qt::MatchStartsWith);
if (i < 0) i = 0; translateFromCombo->setCurrentIndex(language);
translateFromCombo->setCurrentIndex(i);
SaveTranslateFrom(translateFromCombo->currentText()); SaveTranslateFrom(translateFromCombo->currentText());
display->addRow(TRANSLATE_FROM, translateFromCombo); display->addRow(TRANSLATE_FROM, translateFromCombo);
connect(translateFromCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateFrom); connect(translateFromCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateFrom);
for (auto [value, label] : Array<bool&, const char*>{ for (auto [value, label] : Array<bool&, const char*>{
{ translateSelectedOnly, TRANSLATE_SELECTED_THREAD_ONLY }, { translateSelectedOnly, TRANSLATE_SELECTED_THREAD_ONLY },
{ useRateLimiter, RATE_LIMIT_ALL_THREADS }, { rateLimitAll, RATE_LIMIT_ALL_THREADS },
{ rateLimitSelected, RATE_LIMIT_SELECTED_THREAD }, { rateLimitSelected, RATE_LIMIT_SELECTED_THREAD },
{ useCache, USE_TRANS_CACHE }, { useCache, USE_TRANS_CACHE },
{ useFilter, FILTER_GARBAGE } { useFilter, FILTER_GARBAGE }
@ -106,8 +95,8 @@ public:
connect(checkBox, &QCheckBox::clicked, [label, &value](bool checked) { settings.setValue(label, value = checked); }); connect(checkBox, &QCheckBox::clicked, [label, &value](bool checked) { settings.setValue(label, value = checked); });
} }
for (auto [value, label] : Array<int&, const char*>{ for (auto [value, label] : Array<int&, const char*>{
{ tokenCount, MAX_TRANSLATIONS_IN_TIMESPAN }, { tokenCount, RATE_LIMIT_TOKEN_COUNT },
{ rateLimitTimespan, TIMESPAN }, { tokenRestoreDelay, RATE_LIMIT_TOKEN_RESTORE_DELAY },
{ maxSentenceSize, MAX_SENTENCE_SIZE }, { maxSentenceSize, MAX_SENTENCE_SIZE },
}) })
{ {
@ -121,8 +110,8 @@ public:
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);
tlp->authKey = S(keyEdit->text()); authKey->assign(S(keyEdit->text()));
QObject::connect(keyEdit, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(tlp->authKey = S(key))); }); QObject::connect(keyEdit, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(authKey->assign(S(key)))); });
auto keyLabel = new QLabel(QString("<a href=\"%1\">%2</a>").arg(GET_API_KEY_FROM, API_KEY), this); auto keyLabel = new QLabel(QString("<a href=\"%1\">%2</a>").arg(GET_API_KEY_FROM, API_KEY), this);
keyLabel->setOpenExternalLinks(true); keyLabel->setOpenExternalLinks(true);
display->addRow(keyLabel, keyEdit); display->addRow(keyLabel, keyEdit);
@ -130,6 +119,16 @@ public:
setWindowTitle(TRANSLATION_PROVIDER); setWindowTitle(TRANSLATION_PROVIDER);
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection); QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
std::ifstream stream(TRANSLATION_CACHE_FILE, std::ios::binary);
BlockMarkupIterator savedTranslations(stream, Array<std::wstring_view>{ L"|SENTENCE|", L"|TRANSLATION|" });
auto translationCache = ::translationCache.Acquire();
while (auto read = savedTranslations.Next())
{
auto& [sentence, translation] = read.value();
translationCache->try_emplace(std::move(sentence), std::move(translation));
}
savedSize = translationCache->size();
} }
~Window() ~Window()
@ -140,73 +139,62 @@ public:
private: private:
void SaveTranslateTo(QString language) void SaveTranslateTo(QString language)
{ {
SaveCache(); settings.setValue(TRANSLATE_TO, S(translateTo->assign(S(language.split(": ")[1]))));
settings.setValue(TRANSLATE_TO, S(tlp->translateTo = S(language)));
LoadCache();
} }
void SaveTranslateFrom(QString language) void SaveTranslateFrom(QString language)
{ {
settings.setValue(TRANSLATE_FROM, S(tlp->translateFrom = S(language))); settings.setValue(TRANSLATE_FROM, S(translateFrom->assign(S(language.split(": ")[1]))));
} }
} window; } window;
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo) bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
{ {
if (sentenceInfo["text number"] == 0) return false; if (sentenceInfo["text number"] == 0 || sentence.size() > maxSentenceSize) return false;
static class static class
{ {
public: public:
bool Request() bool Request()
{ {
DWORD64 current = GetTickCount64(), token; auto tokens = this->tokens.Acquire();
while (tokens.try_pop(token)) if (token > current - rateLimitTimespan) tokens->push_back(GetTickCount());
{ if (tokens->size() > tokenCount * 5) tokens->erase(tokens->begin(), tokens->begin() + tokenCount * 3);
tokens.push(token); // popped one too many tokens->erase(std::remove_if(tokens->begin(), tokens->end(), [](DWORD token) { return GetTickCount() - token > tokenRestoreDelay; }), tokens->end());
break; return tokens->size() < tokenCount;
}
bool available = tokens.size() < tokenCount;
if (available) tokens.push(current);
return available;
} }
private: private:
concurrency::concurrent_priority_queue<DWORD64, std::greater<DWORD64>> tokens; Synchronized<std::vector<DWORD>> tokens;
} rateLimiter; } rateLimiter;
auto Trim = [](std::wstring& text)
{
text.erase(text.begin(), std::find_if_not(text.begin(), text.end(), iswspace));
text.erase(std::find_if_not(text.rbegin(), text.rend(), iswspace).base(), text.end());
};
bool cache = false; bool cache = false;
std::wstring translation; std::wstring translation;
if (useFilter) if (useFilter)
{ {
Trim(sentence); Trim(sentence);
sentence.erase(std::remove_if(sentence.begin(), sentence.end(), [](wchar_t ch) { return ch < ' ' && ch != '\n'; }), sentence.end()); sentence.erase(std::find_if(sentence.begin(), sentence.end(), [](wchar_t ch) { return ch < ' ' && ch != '\n'; }), sentence.end());
} }
if (sentence.empty()) return true;
if (sentence.size() > maxSentenceSize) translation = SENTENCE_TOO_LARGE_TO_TRANS;
if (useCache) if (useCache)
{ {
auto translationCache = ::translationCache.Acquire(); auto translationCache = ::translationCache.Acquire();
if (auto it = translationCache->find(sentence); it != translationCache->end()) translation = it->second; if (auto it = translationCache->find(sentence); it != translationCache->end()) translation = it->second + L"\x200b"; // dumb hack to not try to translate if stored empty translation
} }
if (translation.empty() && (!translateSelectedOnly || sentenceInfo["current select"])) if (translation.empty() && (!translateSelectedOnly || sentenceInfo["current select"]))
if (rateLimiter.Request() || !useRateLimiter || (!rateLimitSelected && sentenceInfo["current select"])) std::tie(cache, translation) = Translate(sentence, tlp.Copy()); if (rateLimiter.Request() || !rateLimitAll || (!rateLimitSelected && sentenceInfo["current select"])) std::tie(cache, translation) = Translate(sentence);
else translation = TOO_MANY_TRANS_REQUESTS; else translation = TOO_MANY_TRANS_REQUESTS;
if (cache) translationCache->operator[](sentence) = translation;
if (useFilter) Trim(translation); if (useFilter) Trim(translation);
if (cache) translationCache->try_emplace(sentence, translation);
if (cache && translationCache->size() > savedSize + 50) SaveCache();
for (int i = 0; i < translation.size(); ++i) if (translation[i] == '\r' && translation[i + 1] == '\n') translation[i] = 0x200b; // for some reason \r appears as newline - no need to double for (int i = 0; i < translation.size(); ++i) if (translation[i] == '\r' && translation[i + 1] == '\n') translation[i] = 0x200b; // for some reason \r appears as newline - no need to double
if (translation.empty()) translation = TRANSLATION_ERROR; if (!translation.empty()) (sentence += L"\x200b \n") += translation;
(sentence += L"\x200b \n") += translation;
return true; return true;
} }
extern const std::unordered_map<std::wstring, std::wstring> codes; TEST(assert(Translate(L"こんにちは").second.find(L"ello") != std::string::npos));
TEST(
{
assert(Translate(L"こんにちは", { L"English", L"?", L"" }).second.find(L"ello") == 1 || strstr(TRANSLATION_PROVIDER, "DevTools"));
for (auto languages : { languagesFrom, languagesTo }) for (auto language : languages)
assert(codes.count(S(language)));
assert(codes.count(L"?"));
}
);

View File

@ -1,6 +0,0 @@
#pragma once
struct TranslationParam
{
std::wstring translateTo, translateFrom, authKey;
};

View File

@ -1,8 +0,0 @@
add_library(host
host.cpp
textthread.cpp
hookcode.cpp
)
target_precompile_headers(host REUSE_FROM pch)
add_subdirectory(CLI)

View File

@ -2,7 +2,6 @@
#define WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN
#include <Windows.h> #include <Windows.h>
#include <concrt.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include <deque> #include <deque>
@ -16,6 +15,7 @@
#include <optional> #include <optional>
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <shared_mutex>
#include <atomic> #include <atomic>
#include <filesystem> #include <filesystem>
#include <cstdint> #include <cstdint>
@ -76,19 +76,7 @@ private:
M m; M m;
}; };
template <typename F> static struct // should be inline but MSVC (linker) is bugged
void SpawnThread(const F& f) // works in DllMain unlike std thread
{
F* copy = new F(f);
CloseHandle(CreateThread(nullptr, 0, [](void* copy)
{
(*(F*)copy)();
delete (F*)copy;
return 0UL;
}, copy, 0, nullptr));
}
inline struct
{ {
inline static BYTE DUMMY[100]; inline static BYTE DUMMY[100];
template <typename T> operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; } template <typename T> operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
@ -120,12 +108,6 @@ inline std::wstring FormatString(const wchar_t* format, const Args&... args)
} }
#pragma warning(pop) #pragma warning(pop)
inline void Trim(std::wstring& text)
{
text.erase(text.begin(), std::find_if_not(text.begin(), text.end(), iswspace));
text.erase(std::find_if_not(text.rbegin(), text.rend(), iswspace).base(), text.end());
}
inline std::optional<std::wstring> StringToWideString(const std::string& text, UINT encoding) inline std::optional<std::wstring> StringToWideString(const std::string& text, UINT encoding)
{ {
std::vector<wchar_t> buffer(text.size() + 1); std::vector<wchar_t> buffer(text.size() + 1);
@ -152,7 +134,7 @@ template <typename... Args>
inline void TEXTRACTOR_MESSAGE(const wchar_t* format, const Args&... args) { MessageBoxW(NULL, FormatString(format, args...).c_str(), L"Textractor", MB_OK); } inline void TEXTRACTOR_MESSAGE(const wchar_t* format, const Args&... args) { MessageBoxW(NULL, FormatString(format, args...).c_str(), L"Textractor", MB_OK); }
template <typename... Args> template <typename... Args>
inline void TEXTRACTOR_DEBUG(const wchar_t* format, const Args&... args) { SpawnThread([=] { TEXTRACTOR_MESSAGE(format, args...); }); } inline void TEXTRACTOR_DEBUG(const wchar_t* format, const Args&... args) { std::thread([=] { TEXTRACTOR_MESSAGE(format, args...); }).detach(); }
void Localize(); void Localize();

View File

@ -10,8 +10,7 @@ DefaultGroupName=Textractor
MinVersion=6.1 MinVersion=6.1
OutputBaseFilename=Textractor-{#VERSION}-Setup OutputBaseFilename=Textractor-{#VERSION}-Setup
OutputDir=Builds OutputDir=Builds
PrivilegesRequired=admin PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog
SolidCompression=yes SolidCompression=yes
Uninstallable=no Uninstallable=no
@ -42,4 +41,3 @@ Source: "Builds\Textractor-Thai-{#VERSION}\*"; DestDir: "{app}"; Languages: th;
Source: "Builds\Textractor-Korean-{#VERSION}\*"; DestDir: "{app}"; Languages: ko; Flags: recursesubdirs ignoreversion Source: "Builds\Textractor-Korean-{#VERSION}\*"; DestDir: "{app}"; Languages: ko; Flags: recursesubdirs ignoreversion
Source: "Builds\Textractor-Italian-{#VERSION}\*"; DestDir: "{app}"; Languages: it; Flags: recursesubdirs ignoreversion Source: "Builds\Textractor-Italian-{#VERSION}\*"; DestDir: "{app}"; Languages: it; Flags: recursesubdirs ignoreversion
Source: "Builds\Textractor-French-{#VERSION}\*"; DestDir: "{app}"; Languages: fr; Flags: recursesubdirs ignoreversion Source: "Builds\Textractor-French-{#VERSION}\*"; DestDir: "{app}"; Languages: fr; Flags: recursesubdirs ignoreversion
Source: "INSTALL_THIS_UNICODE_FONT.ttf"; DestDir: "{autofonts}"; DestName: "ARIAL_UNICODE_MS.ttf"; FontInstall: "Arial Unicode MS";

230
text.cpp
View File

@ -33,8 +33,8 @@ const char* SETTINGS = u8"Settings";
const char* EXTENSIONS = u8"Extensions"; const char* EXTENSIONS = u8"Extensions";
const char* SELECT_PROCESS = u8"Select process"; const char* SELECT_PROCESS = u8"Select process";
const char* ATTACH_INFO = u8R"(If you don't see the process you want to attach, try running with admin rights const char* ATTACH_INFO = u8R"(If you don't see the process you want to attach, try running with admin rights
You can also type in the process ID)"; You can also type in the process id)";
const char* SELECT_PROCESS_INFO = u8"If you manually type in the process file name, use the absolute path"; const char* SELECT_PROCESS_INFO = u8"If you manually type in the process file name, please use the absolute path";
const char* FROM_COMPUTER = u8"Select from computer"; const char* FROM_COMPUTER = u8"Select from computer";
const char* PROCESSES = u8"Processes (*.exe)"; const char* PROCESSES = u8"Processes (*.exe)";
const char* CODE_INFODUMP = u8R"(Enter read code const char* CODE_INFODUMP = u8R"(Enter read code
@ -99,14 +99,14 @@ const wchar_t* CONSOLE = L"Console";
const wchar_t* CLIPBOARD = L"Clipboard"; const wchar_t* CLIPBOARD = L"Clipboard";
const wchar_t* ABOUT = L"Textractor " ARCH L" v" VERSION LR"( made by Artikash (email: akashmozumdar@gmail.com) const wchar_t* ABOUT = L"Textractor " ARCH L" v" VERSION LR"( made by Artikash (email: akashmozumdar@gmail.com)
Project homepage: https://github.com/Artikash/Textractor Project homepage: https://github.com/Artikash/Textractor
Tutorial video: https://github.com/Artikash/Textractor/blob/master/docs/TUTORIAL.md Tutorial video: https://tinyurl.com/textractor-tutorial
FAQ: https://github.com/Artikash/Textractor/wiki/FAQ FAQ: https://github.com/Artikash/Textractor/wiki/FAQ
Please contact Artikash with any problems, feature requests, or questions relating to Textractor Please contact Artikash with any problems, feature requests, or questions relating to Textractor
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 [-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";
const wchar_t* NEED_32_BIT = L"Textractor: architecture mismatch: only Textractor x86 can inject this process"; const wchar_t* NEED_32_BIT = L"Textractor: architecture mismatch: only Textractor x86 can inject this process";
@ -138,12 +138,11 @@ const char* TRANSLATE_TO = u8"Translate to";
const char* TRANSLATE_FROM = u8"Translate from"; const char* TRANSLATE_FROM = u8"Translate from";
const char* FILTER_GARBAGE = u8"Filter garbage characters"; const char* FILTER_GARBAGE = u8"Filter garbage characters";
const char* TRANSLATE_SELECTED_THREAD_ONLY = u8"Translate selected text thread only"; const char* TRANSLATE_SELECTED_THREAD_ONLY = u8"Translate selected text thread only";
const char* RATE_LIMIT_ALL_THREADS = u8"Use rate limiter"; const char* RATE_LIMIT_ALL_THREADS = u8"Rate limit all text threads";
const char* RATE_LIMIT_SELECTED_THREAD = u8"Rate limit selected text thread"; const char* RATE_LIMIT_SELECTED_THREAD = u8"Rate limit selected text thread";
const char* USE_TRANS_CACHE = u8"Use translation cache"; const char* USE_TRANS_CACHE = u8"Use translation cache";
const char* MAX_TRANSLATIONS_IN_TIMESPAN = u8"Max translation requests in timespan"; const char* RATE_LIMIT_TOKEN_COUNT = u8"Rate limiter token count";
const char* TIMESPAN = u8"Timespan (ms)"; const char* RATE_LIMIT_TOKEN_RESTORE_DELAY = u8"Rate limiter token restore delay (ms)";
const wchar_t* SENTENCE_TOO_LARGE_TO_TRANS = L"Sentence too large to translate";
const wchar_t* TOO_MANY_TRANS_REQUESTS = L"Rate limit exceeded: refuse to make more translation requests"; const wchar_t* TOO_MANY_TRANS_REQUESTS = L"Rate limit exceeded: refuse to make more translation requests";
const wchar_t* TRANSLATION_ERROR = L"Error while translating"; const wchar_t* TRANSLATION_ERROR = L"Error while translating";
const char* USE_PREV_SENTENCE_CONTEXT = u8"Use previous sentence as context"; const char* USE_PREV_SENTENCE_CONTEXT = u8"Use previous sentence as context";
@ -151,12 +150,13 @@ const char* API_KEY = u8"API key";
const char* CHROME_LOCATION = u8"Google Chrome file location"; const char* CHROME_LOCATION = u8"Google Chrome file location";
const char* START_DEVTOOLS = u8"Start DevTools"; const char* START_DEVTOOLS = u8"Start DevTools";
const char* STOP_DEVTOOLS = u8"Stop DevTools"; const char* STOP_DEVTOOLS = u8"Stop DevTools";
const char* HIDE_CHROME = u8"Hide Chrome window"; const char* HEADLESS_MODE = u8"Headless mode";
const char* DEVTOOLS_STATUS = u8"DevTools status"; const char* DEVTOOLS_STATUS = u8"DevTools status";
const char* AUTO_START = u8"Start automatically"; const char* AUTO_START = u8"Start automatically";
const wchar_t* ERROR_START_CHROME = L"failed to start Chrome or to connect to it"; const wchar_t* ERROR_START_CHROME = L"failed to start Chrome or to connect to it";
const char* EXTRA_WINDOW_INFO = u8R"(Right click to change settings const char* EXTRA_WINDOW_INFO = u8R"(Right click to change settings
Click and drag on window edges to move, or the bottom right corner to resize)"; Click and drag on window edges to move, or the bottom right corner to resize)";
const char* SENTENCE_TOO_BIG = u8"Sentence too large to display";
const char* MAX_SENTENCE_SIZE = u8"Max sentence size"; const char* MAX_SENTENCE_SIZE = u8"Max sentence size";
const char* TOPMOST = u8"Always on top"; const char* TOPMOST = u8"Always on top";
const char* DICTIONARY = u8"Dictionary"; const char* DICTIONARY = u8"Dictionary";
@ -178,13 +178,7 @@ This file must be encoded in UTF-8.)";
const char* SHOW_ORIGINAL = u8"Original text"; const char* SHOW_ORIGINAL = u8"Original text";
const char* SHOW_ORIGINAL_INFO = u8R"(Original text will not be shown const char* SHOW_ORIGINAL_INFO = u8R"(Original text will not be shown
Only works if this extension is used directly after a translation extension)"; Only works if this extension is used directly after a translation extension)";
const char* ORIGINAL_AFTER_TRANSLATION = u8"Original text after translation";
const char* SIZE_LOCK = u8"Size lock"; const char* SIZE_LOCK = u8"Size lock";
const char* POSITION_LOCK = u8"Position lock";
const char* CENTERED_TEXT = u8"Centered text";
const char* AUTO_RESIZE_WINDOW_HEIGHT = u8"Auto resize window height";
const char* CLICK_THROUGH = u8"Click through\tAlt+X";
const char* HIDE_MOUSEOVER = u8"Hide while mouse on top";
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";
@ -207,7 +201,7 @@ Modifications to global variables from ProcessSentence are not guaranteed to per
Properties in sentenceInfo: Properties in sentenceInfo:
"current select": 0 unless sentence is in the text thread currently selected by the user. "current select": 0 unless sentence is in the text thread currently selected by the user.
"process id": process ID that the sentence is coming from. 0 for console and clipboard. "process id": process id that the sentence is coming from. 0 for console and clipboard.
"text number": number of the current text thread. Counts up one by one as text threads are created. 0 for console, 1 for clipboard. "text number": number of the current text thread. Counts up one by one as text threads are created. 0 for console, 1 for clipboard.
--]] --]]
function ProcessSentence(sentence, sentenceInfo) function ProcessSentence(sentence, sentenceInfo)
@ -225,20 +219,8 @@ All text in this file outside of a replacement command is ignored.
A caret (^) acts as a wildcard that matches any other single character. A caret (^) acts as a wildcard that matches any other single character.
Whitespace in original_text is ignored, but replacement_text can contain spaces, newlines, etc. Whitespace in original_text is ignored, but replacement_text can contain spaces, newlines, etc.
This file must be encoded in Unicode (UTF-16 Little Endian).)"; This file must be encoded in Unicode (UTF-16 Little Endian).)";
const wchar_t* REGEX_REPLACER_INSTRUCTIONS = LR"(This file only does anything when the "Regex Replacer" extension is used.
Replacement commands must be formatted like this:
|REGEX|regular_expression|BECOMES|replacement_text|MODIFIER|modifiers|END|
replacement_text can reference capture groups with a $ followed by their number (e.g. $1 references first capture group).
modifiers can contain the following:
"g" the replacement is global.
"i" the replacement ignores the case.
If empty the replacement is only for the first match and case sensitive.
All text in this file outside of a replacement command is ignored.
This file must be encoded in Unicode (UTF-16 Little Endian).
Learn, build, & test Regular Expressions: https://regexr.com/)";
const char* THREAD_LINKER = u8"Thread Linker"; const char* THREAD_LINKER = u8"Thread Linker";
const char* LINK = u8"Link"; 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_FROM = u8"Thread number to link from";
const char* THREAD_LINK_TO = u8"Thread number to link to"; const char* THREAD_LINK_TO = u8"Thread number to link to";
const char* HEXADECIMAL = u8"Hexadecimal"; const char* HEXADECIMAL = u8"Hexadecimal";
@ -305,7 +287,7 @@ Presiona supr en una extension seleccionada para removerla)";
CLIPBOARD = L"Portapapeles"; CLIPBOARD = L"Portapapeles";
ABOUT = L"Textractor " ARCH L" v" VERSION LR"( hecho por mí: Artikash (correo: akashmozumdar@gmail.com) ABOUT = L"Textractor " ARCH L" v" VERSION LR"( hecho por mí: Artikash (correo: akashmozumdar@gmail.com)
Página del proyecto: https://github.com/Artikash/Textractor Página del proyecto: https://github.com/Artikash/Textractor
Video tutorial: https://github.com/Artikash/Textractor/blob/master/docs/TUTORIAL.md Video tutorial: https://tinyurl.com/textractor-tutorial
No dudes en conectarme si tienes algún problema, petición de característica o preguntas relacionadas con Textractor No dudes en conectarme si tienes algún problema, petición de característica o preguntas relacionadas con Textractor
Puedes hacerlo en la página del proyecto (en el apartado de "Issues") o por correo. Usa el inglés para comunicarte. Puedes hacerlo en la página del proyecto (en el apartado de "Issues") o por correo. Usa el inglés para comunicarte.
Código fuente disponible bajo GPLv3 en la página del proyecto)"; Código fuente disponible bajo GPLv3 en la página del proyecto)";
@ -341,7 +323,7 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
#endif // SPANISH #endif // SPANISH
#ifdef SIMPLIFIED_CHINESE #ifdef SIMPLIFIED_CHINESE
NATIVE_LANGUAGE = "Chinese (Simplified)"; NATIVE_LANGUAGE = "Chinese (simplified)";
ATTACH = u8"附加到游戏"; ATTACH = u8"附加到游戏";
LAUNCH = u8"启动游戏"; LAUNCH = u8"启动游戏";
CONFIG = u8"配置游戏"; CONFIG = u8"配置游戏";
@ -354,30 +336,28 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
SETTINGS = u8"设置"; SETTINGS = u8"设置";
EXTENSIONS = u8"扩展"; EXTENSIONS = u8"扩展";
SELECT_PROCESS = u8"选择进程"; SELECT_PROCESS = u8"选择进程";
ATTACH_INFO = u8R"(如果没看见想要附加的进程,尝试使用管理员权限运行Textractor ATTACH_INFO = u8R"(如果没看见想要附加的进程,尝试使用管理员权限运行
ID)"; ID)";
SELECT_PROCESS_INFO = u8"如果手动输入游戏名,请使用绝对路径"; SELECT_PROCESS_INFO = u8"如果手动输入游戏名,请使用绝对路径";
FROM_COMPUTER = u8"资源管理器中选择"; FROM_COMPUTER = u8"计算机中选择";
PROCESSES = u8"可执行文件 (*.exe)"; PROCESSES = u8"进程 (*.exe)";
SAVE_SETTINGS = u8"保存设置"; SAVE_SETTINGS = u8"保存设置";
EXTEN_WINDOW_INSTRUCTIONS = u8R"(在列表中单击右键以添加或移除扩展 EXTEN_WINDOW_INSTRUCTIONS = u8R"(从计算机拖拽扩展 (.dll) 文件到这里来添加
, (使)
)";
使 delete )";
ADD_EXTENSION = u8"添加扩展"; ADD_EXTENSION = u8"添加扩展";
REMOVE_EXTENSION = u8"移除扩展";
INVALID_EXTENSION = u8"%1 是一个无效扩展"; INVALID_EXTENSION = u8"%1 是一个无效扩展";
CONFIRM_EXTENSION_OVERWRITE = u8"此扩展的另一个版本已启用,是否删除并覆盖它?"; CONFIRM_EXTENSION_OVERWRITE = u8"此扩展的另一个版本已启用,是否删除并覆盖它?";
EXTENSION_WRITE_ERROR = u8"保存扩展失败"; EXTENSION_WRITE_ERROR = u8"保存扩展失败";
USE_JP_LOCALE = u8"模拟日本区域设置?"; USE_JP_LOCALE = u8"模拟日本区域设置?";
FAILED_TO_CREATE_CONFIG_FILE = u8"无法创建配置文件 \"%1\"";
HOOK_SEARCH_UNSTABLE_WARNING = u8"搜索钩子的功能是不稳定的! 可能会导致你的游戏崩溃! "; HOOK_SEARCH_UNSTABLE_WARNING = u8"搜索钩子的功能是不稳定的! 可能会导致你的游戏崩溃! ";
HOOK_SEARCH_STARTING_VIEW_CONSOLE = u8"正在初始化钩子搜索 - 请查看控制台以获取更多提示";
SEARCH_CJK = u8"搜索中文/日文/韩文"; SEARCH_CJK = u8"搜索中文/日文/韩文";
SEARCH_PATTERN = u8"搜索匹配特征 (hex byte array)"; SEARCH_PATTERN = u8"搜索匹配特征 (hex byte array)";
SEARCH_DURATION = u8"搜索持续时间 (ms)"; SEARCH_DURATION = u8"搜索持续时间 (ms)";
SEARCH_MODULE = u8"搜索指定模块"; SEARCH_MODULE = u8"搜索指定模块";
PATTERN_OFFSET = u8"相对于特征地址的偏移值"; PATTERN_OFFSET = u8"相对于特征地址的偏移值";
MAX_HOOK_SEARCH_RECORDS = u8"搜索结果达到上限"; MAX_HOOK_SEARCH_RECORDS = u8"搜索结果上限";
MIN_ADDRESS = u8"起始地址 (hex)"; MIN_ADDRESS = u8"起始地址 (hex)";
MAX_ADDRESS = u8"结束地址 (hex)"; MAX_ADDRESS = u8"结束地址 (hex)";
STRING_OFFSET = u8"字符串偏移值 (hex)"; STRING_OFFSET = u8"字符串偏移值 (hex)";
@ -391,7 +371,7 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
DOUBLE_CLICK_TO_REMOVE_HOOK = u8"双击移除不需要的钩子"; DOUBLE_CLICK_TO_REMOVE_HOOK = u8"双击移除不需要的钩子";
FILTER_REPETITION = u8"过滤重复文本"; FILTER_REPETITION = u8"过滤重复文本";
AUTO_ATTACH = u8"自动附加"; AUTO_ATTACH = u8"自动附加";
ATTACH_SAVED_ONLY = u8"自动附加 (仅限保存过配置的游戏)"; ATTACH_SAVED_ONLY = u8"自动附加 (saved only)";
SHOW_SYSTEM_PROCESSES = u8"显示系统进程"; SHOW_SYSTEM_PROCESSES = u8"显示系统进程";
DEFAULT_CODEPAGE = u8"默认代码页"; DEFAULT_CODEPAGE = u8"默认代码页";
FLUSH_DELAY = u8"刷新延迟"; FLUSH_DELAY = u8"刷新延迟";
@ -402,9 +382,9 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
CLIPBOARD = L"剪贴板"; CLIPBOARD = L"剪贴板";
ABOUT = L"Textractor " ARCH L" v" VERSION LR"( 作者: Artikash (email: akashmozumdar@gmail.com) ABOUT = L"Textractor " ARCH L" v" VERSION LR"( 作者: Artikash (email: akashmozumdar@gmail.com)
: https://github.com/Artikash/Textractor : https://github.com/Artikash/Textractor
: https://github.com/Artikash/Textractor/blob/master/docs/TUTORIAL.md : https://tinyurl.com/textractor-tutorial
Textractor Textractor
(Issues ) ()
GPLv3 )"; GPLv3 )";
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: 已经注入";
@ -420,8 +400,7 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
HOOK_FAILED = u8"Textractor: 钩子注入失败"; HOOK_FAILED = u8"Textractor: 钩子注入失败";
TOO_MANY_HOOKS = u8"Textractor: 钩子数量已达上限: 无法注入"; TOO_MANY_HOOKS = u8"Textractor: 钩子数量已达上限: 无法注入";
HOOK_SEARCH_STARTING = u8"Textractor: 开始搜索钩子"; HOOK_SEARCH_STARTING = u8"Textractor: 开始搜索钩子";
HOOK_SEARCH_INITIALIZING = u8"Textractor: 初始化钩子搜索 (%f%%)"; NOT_ENOUGH_TEXT = u8"Textractor: 没有足够的文本用来精确搜索";
NOT_ENOUGH_TEXT = u8"Textractor: 文本长度不足, 无法精确搜索";
HOOK_SEARCH_INITIALIZED = u8"Textractor: 搜索初始化完成, 创建了 %zd 个钩子"; HOOK_SEARCH_INITIALIZED = u8"Textractor: 搜索初始化完成, 创建了 %zd 个钩子";
MAKE_GAME_PROCESS_TEXT = u8"Textractor: 请点击游戏区域, 在接下来的 %d 秒内使游戏强制处理文本"; MAKE_GAME_PROCESS_TEXT = u8"Textractor: 请点击游戏区域, 在接下来的 %d 秒内使游戏强制处理文本";
HOOK_SEARCH_FINISHED = u8"Textractor: 钩子搜索完毕, 找到了 %d 条结果"; HOOK_SEARCH_FINISHED = u8"Textractor: 钩子搜索完毕, 找到了 %d 条结果";
@ -434,65 +413,47 @@ Clic y arrastra los bordes de la ventana para moverla, o en la esquina inferior
HIJACK_ERROR = u8"Textractor: Hijack 错误"; HIJACK_ERROR = u8"Textractor: Hijack 错误";
COULD_NOT_FIND = u8"Textractor: 无法找到文本"; COULD_NOT_FIND = u8"Textractor: 无法找到文本";
TRANSLATE_TO = u8"翻译为"; TRANSLATE_TO = u8"翻译为";
TRANSLATE_FROM = u8"原文语言";
FILTER_GARBAGE = u8"过滤无意义字符";
TRANSLATE_SELECTED_THREAD_ONLY = u8"仅翻译当前选择的文本线程"; TRANSLATE_SELECTED_THREAD_ONLY = u8"仅翻译当前选择的文本线程";
RATE_LIMIT_ALL_THREADS = u8"限制请求频率"; RATE_LIMIT_ALL_THREADS = u8"限制所有文本线程的请求频率";
RATE_LIMIT_SELECTED_THREAD = u8"限制选中线程的请求频率"; RATE_LIMIT_SELECTED_THREAD = u8"限制当前选择线程的请求频率";
USE_TRANS_CACHE = u8"使用缓存的翻译结果"; USE_TRANS_CACHE = u8"使用缓存的翻译结果";
MAX_TRANSLATIONS_IN_TIMESPAN = u8"单位时间内最大请求次"; RATE_LIMIT_TOKEN_COUNT = u8"限流器令牌";
TIMESPAN = u8"单位时间长度 (ms)"; RATE_LIMIT_TOKEN_RESTORE_DELAY = u8"限流器令牌重置时间 (ms)";
TOO_MANY_TRANS_REQUESTS = L"超出频率限制: 拒绝发出翻译请求"; TOO_MANY_TRANS_REQUESTS = L"太多翻译请求: 拒绝生成更多";
TRANSLATION_ERROR = L"翻译时出错"; TRANSLATION_ERROR = L"翻译时出错";
USE_PREV_SENTENCE_CONTEXT = u8"使用之前的句子作为上下文"; USE_PREV_SENTENCE_CONTEXT = u8"使用之前的句子作为上下文";
API_KEY = u8"API key"; API_KEY = u8"API key";
CHROME_LOCATION = u8"Google Chrome 安装位置"; EXTRA_WINDOW_INFO = u8R"(右键修改设置
START_DEVTOOLS = u8"启动 DevTools"; )";
STOP_DEVTOOLS = u8"停止 DevTools"; SENTENCE_TOO_BIG = u8"文本过长无法显示";
HIDE_CHROME = u8"隐藏 Chrome 窗口";
DEVTOOLS_STATUS = u8"DevTools 状态";
AUTO_START = u8"自动启动";
ERROR_START_CHROME = L"无法启动或连接到 Chrome";
EXTRA_WINDOW_INFO = u8R"(在此点击右键以修改设置
, )";
MAX_SENTENCE_SIZE = u8"最大文本长度"; MAX_SENTENCE_SIZE = u8"最大文本长度";
TOPMOST = u8"窗口总是置顶"; TOPMOST = u8"窗口总是置顶";
DICTIONARY = u8"字典"; DICTIONARY = u8"字典";
SHOW_ORIGINAL = u8"显示原文"; SHOW_ORIGINAL = u8"显示原文";
SHOW_ORIGINAL_INFO = u8R"(原文将被隐藏 SHOW_ORIGINAL_INFO = u8R"(将不显示原文
使)"; 使)";
ORIGINAL_AFTER_TRANSLATION = u8"把原文显示在翻译文本的后面";
SIZE_LOCK = u8"锁定窗口大小";
POSITION_LOCK = u8"锁定窗口位置";
CENTERED_TEXT = u8"居中显示文本";
AUTO_RESIZE_WINDOW_HEIGHT = u8"自动改变窗口高度";
CLICK_THROUGH = u8"点击穿透\tAlt+X";
HIDE_MOUSEOVER = u8"鼠标经过时隐藏窗口";
OPACITY = u8"透明度"; OPACITY = u8"透明度";
SIZE_LOCK = u8"锁定窗口大小";
BG_COLOR = u8"背景颜色"; BG_COLOR = u8"背景颜色";
TEXT_COLOR = u8"文本颜色"; TEXT_COLOR = u8"文本颜色";
TEXT_OUTLINE = u8"文字"; TEXT_OUTLINE = u8"文字边框";
OUTLINE_COLOR = u8"文字描边颜色"; OUTLINE_COLOR = u8"边框颜色";
OUTLINE_SIZE = u8"文字描边大小"; OUTLINE_SIZE = u8"边框大小";
OUTLINE_SIZE_INFO = u8"描边的单位为px (建议保持在字体大小的20%以下)"; OUTLINE_SIZE_INFO = u8"以像素为单位的大小(建议保持在字体大小的20%以下)";
FONT = u8"字体"; FONT = u8"字体";
LUA_INTRO = u8R"(--[[ LUA_INTRO = u8R"(--[[
ProcessSentence Textractor每接收到一句文本时都会调用的函数. ProcessSentence Textractor每接收到一句文本时都会调用的函数
sentence: Textractor接收到的文本 (UTF-8), string. sentence: Textractor接收到的文本 (UTF-8)
sentenceInfo: , tabel. sentenceInfo: table
. nil
nil, . 使Lua解释器的副本用于保证线程安全
ProcessSentence函数中对全局变量的修改可能不会生效
使Lua解释器的副本用于保证线程安全. sentenceInfo有以下成员:
ProcessSentence . "current select": 线0
"process id": id. 0 线
sentenceInfo : "text number": 线id. 线 0 , 1
current select: 线, 0. number.
process id: ID. 0 线. number.
text number: 线ID. 线. 0 , 1 . number.
--]] --]]
function ProcessSentence(sentence, sentenceInfo) function ProcessSentence(sentence, sentenceInfo)
--... --...
end)"; end)";
@ -504,28 +465,13 @@ end)";
REPLACER_INSTRUCTIONS = LR"(使用"Replace"扩展时会使用此文件 REPLACER_INSTRUCTIONS = LR"(使用"Replace"扩展时会使用此文件
: :
|ORIG||BECOMES||END| |ORIG||BECOMES||END|
. .
^ .
. .
使 Unicode (UTF-16 little endian).)"; 使Unicode字符集(UTF-16 little endian).)";
REGEX_REPLACER_INSTRUCTIONS = LR"(使用"Regex Replace"扩展时会使用此文件
:
|REGEX||BECOMES||MODIFIER||END|
$[] (: $1 1).
:
g .
i .
, 1, .
.
使 Unicode (UTF-16 little endian).
线:
https://www.runoob.com/regexp/regexp-intro.html
https://regexr.com/)";
THREAD_LINKER = u8"线程链接器"; THREAD_LINKER = u8"线程链接器";
LINK = u8"链接"; LINK = u8"链接";
UNLINK = u8"断开"; THREAD_LINK_FROM = u8"需要链接的线程id";
THREAD_LINK_FROM = u8"需要链接的线程ID"; THREAD_LINK_TO = u8"链接到的线程id";
THREAD_LINK_TO = u8"链接到的线程ID";
HEXADECIMAL = u8"十六进制"; HEXADECIMAL = u8"十六进制";
#endif // SIMPLIFIED_CHINESE #endif // SIMPLIFIED_CHINESE
@ -544,7 +490,7 @@ i 忽略大小写差异.
EXTENSIONS = u8"Расширения"; EXTENSIONS = u8"Расширения";
SELECT_PROCESS = u8"Выберете процесс"; SELECT_PROCESS = u8"Выберете процесс";
ATTACH_INFO = u8R"(Если вы не видите процесс, к которому хотите присоединить, попробуйте запуск с правами администратора ATTACH_INFO = u8R"(Если вы не видите процесс, к которому хотите присоединить, попробуйте запуск с правами администратора
Вы также можете ввести ID процесса)"; Вы также можете ввести id процесса)";
SELECT_PROCESS_INFO = u8"При ручном вводе имени файла процесса используйте абсолютный путь"; SELECT_PROCESS_INFO = u8"При ручном вводе имени файла процесса используйте абсолютный путь";
FROM_COMPUTER = u8"Найти в проводнике"; FROM_COMPUTER = u8"Найти в проводнике";
PROCESSES = u8"Процессы (*.exe)"; PROCESSES = u8"Процессы (*.exe)";
@ -609,13 +555,13 @@ padding: длина добавочных данных перед строкой
CLIPBOARD = L"Буфер обмена"; CLIPBOARD = L"Буфер обмена";
ABOUT = L"Textractor " ARCH L" в." VERSION LR"( автор: Artikash (email: akashmozumdar@gmail.com) ABOUT = L"Textractor " ARCH L" в." VERSION LR"( автор: Artikash (email: akashmozumdar@gmail.com)
Домашняя страница: https://github.com/Artikash/Textractor Домашняя страница: https://github.com/Artikash/Textractor
Обучающее видео: https://github.com/Artikash/Textractor/blob/master/docs/TUTORIAL.md Обучающее видео: https://tinyurl.com/textractor-tutorial
Сообщайте о любых проблемах, желаемых для добавления функциях, или задавайте вопросы, касающиеся Textractor Сообщайте о любых проблемах, желаемых для добавления функциях, или задавайте вопросы, касающиеся Textractor
Сделать это вы можете на домашней странице (секция issues) или через электронную почту Сделать это вы можете на домашней странице (секция issues) или через электронную почту
Исходный код доступен по лицензии GPLv3 на домашней странице проекта Исходный код доступен по лицензии GPLv3 на домашней странице проекта
Если эта программа вам понравилась, расскажите всем о ней :))"; Если эта программа вам понравилась, расскажите всем о ней :))";
CL_OPTIONS = LR"(использование: Textractor [-p{process ID|"process name"}]... CL_OPTIONS = LR"(использование: Textractor [-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: уже присоединен";
NEED_32_BIT = L"Textractor: несоответствие архитектуры: попробуйте Textractor x86 вместо этого"; NEED_32_BIT = L"Textractor: несоответствие архитектуры: попробуйте Textractor x86 вместо этого";
@ -646,14 +592,15 @@ padding: длина добавочных данных перед строкой
RATE_LIMIT_ALL_THREADS = u8"Ограничение скорости для всех текстовых потоков"; RATE_LIMIT_ALL_THREADS = u8"Ограничение скорости для всех текстовых потоков";
RATE_LIMIT_SELECTED_THREAD = u8"Ограничение скорости выделенного текстового потока"; RATE_LIMIT_SELECTED_THREAD = u8"Ограничение скорости выделенного текстового потока";
USE_TRANS_CACHE = u8"Использовать кеш перевода"; USE_TRANS_CACHE = u8"Использовать кеш перевода";
MAX_TRANSLATIONS_IN_TIMESPAN = u8"Ограничение частоты подсчёта токенов"; RATE_LIMIT_TOKEN_COUNT = u8"Ограничение частоты подсчёта токенов";
TIMESPAN = u8"Ограничение частоты задержки восстановления токенов (мс)"; RATE_LIMIT_TOKEN_RESTORE_DELAY = u8"Ограничение частоты задержки восстановления токенов (мс)";
TOO_MANY_TRANS_REQUESTS = L"Слишком много запросов для перевода: отклонено"; TOO_MANY_TRANS_REQUESTS = L"Слишком много запросов для перевода: отклонено";
TRANSLATION_ERROR = L"Ошибка при переводе"; TRANSLATION_ERROR = L"Ошибка при переводе";
USE_PREV_SENTENCE_CONTEXT = u8"Использовать предыдущее предложение как контекст"; USE_PREV_SENTENCE_CONTEXT = u8"Использовать предыдущее предложение как контекст";
API_KEY = u8"Ключ API"; API_KEY = u8"Ключ API";
EXTRA_WINDOW_INFO = u8R"(Правый клик для изменения настроек EXTRA_WINDOW_INFO = u8R"(Правый клик для изменения настроек
Нажмите и перетащите за края - для перемещения, или за правый-нижний угол - для изменения размера)"; Нажмите и перетащите за края - для перемещения, или за правый-нижний угол - для изменения размера)";
SENTENCE_TOO_BIG = u8"Придложение слишком длинное для отображения";
MAX_SENTENCE_SIZE = u8"Максимальная длина предложения"; MAX_SENTENCE_SIZE = u8"Максимальная длина предложения";
TOPMOST = u8"Поверх всех окон"; TOPMOST = u8"Поверх всех окон";
DICTIONARY = u8"Словарь"; DICTIONARY = u8"Словарь";
@ -698,7 +645,7 @@ Param sentenceInfo: таблица различной информации о п
Параметры в sentenceInfo: Параметры в sentenceInfo:
"current select": равно 0, если предложение не находится в текстовой нити, выбранной в данный момент пользователем. "current select": равно 0, если предложение не находится в текстовой нити, выбранной в данный момент пользователем.
"process id": ID процесса, из которого предложение поступило. Равно 0, когда это консоль или буфер обмена. "process id": id процесса, из которого предложение поступило. Равно 0, когда это консоль или буфер обмена.
"text number": номер текущей текстовой нити. Растет один за другим по мере создания текстовых нитей. 0 для консоли, 1 для буфера обмена. "text number": номер текущей текстовой нити. Растет один за другим по мере создания текстовых нитей. 0 для консоли, 1 для буфера обмена.
--]] --]]
function ProcessSentence(sentence, sentenceInfo) function ProcessSentence(sentence, sentenceInfo)
@ -732,7 +679,7 @@ end)";
EXTENSIONS = u8"Ekstensi"; EXTENSIONS = u8"Ekstensi";
SELECT_PROCESS = u8"Pilih Proses"; SELECT_PROCESS = u8"Pilih Proses";
ATTACH_INFO = u8R"(Jika kamu tidak dapat melihat proses yang akan ditempelkan, coba menjalankan dengan mode administrator ATTACH_INFO = u8R"(Jika kamu tidak dapat melihat proses yang akan ditempelkan, coba menjalankan dengan mode administrator
Kamu juga dapat mengetik process ID game yang akan ditempel)"; Kamu juga dapat mengetik process id game yang akan ditempel)";
FROM_COMPUTER = u8"Pilih dari komputer"; FROM_COMPUTER = u8"Pilih dari komputer";
PROCESSES = u8"Proses (*.exe)"; PROCESSES = u8"Proses (*.exe)";
SAVE_SETTINGS = u8"Simpan pengaturan"; SAVE_SETTINGS = u8"Simpan pengaturan";
@ -748,7 +695,7 @@ Tekan delete pada ekstensi yang dipilih untuk menghapus ekstensi)";
CLIPBOARD = L"Papan clipboard"; CLIPBOARD = L"Papan clipboard";
ABOUT = L"Textractor " ARCH L" v" VERSION LR"( dibuat oleh saya: Artikash (email: akashmozumdar@gmail.com) ABOUT = L"Textractor " ARCH L" v" VERSION LR"( dibuat oleh saya: Artikash (email: akashmozumdar@gmail.com)
Halaman project: https://github.com/Artikash/Textractor Halaman project: https://github.com/Artikash/Textractor
Video tutorial : https://github.com/Artikash/Textractor/blob/master/docs/TUTORIAL.md Video tutorial : https://tinyurl.com/textractor-tutorial
Tolong hubungi saya jika kamu memiliki masalah terkait masalah, permintaan fitur, atau pertanyaan terkait Textractor Tolong hubungi saya jika kamu memiliki masalah terkait masalah, permintaan fitur, atau pertanyaan terkait Textractor
Kamu dapat melakukannya lewat halaman utama project (bagian issues) atau lewat email Kamu dapat melakukannya lewat halaman utama project (bagian issues) atau lewat email
Source code tersedia dibawah lisensi GPLv3 di halaman utama project Source code tersedia dibawah lisensi GPLv3 di halaman utama project
@ -865,13 +812,13 @@ Per rimuovere un estenzione, selezionala e premi rimuovi)";
CLIPBOARD = L"Appunti"; CLIPBOARD = L"Appunti";
ABOUT = L"Textractor " ARCH L" v" VERSION LR"( creato da me: Artikash (email: akashmozumdar@gmail.com) ABOUT = L"Textractor " ARCH L" v" VERSION LR"( creato da me: Artikash (email: akashmozumdar@gmail.com)
Pagina principale del progetto: https://github.com/Artikash/Textractor Pagina principale del progetto: https://github.com/Artikash/Textractor
Video tutorial: https://github.com/Artikash/Textractor/blob/master/docs/TUTORIAL.md Video tutorial: https://tinyurl.com/textractor-tutorial
Contattatemi per ogni problema, richiesta futura, o domande legate a Textractor Contattatemi per ogni problema, richiesta futura, o domande legate a Textractor
Puoi farlo attraverso la pagina principale del progetto (sezione issues) o via email Puoi farlo attraverso la pagina principale del progetto (sezione issues) o via email
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 [-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";
@ -904,14 +851,15 @@ esempio: Textractor -p4466 -p"My Game.exe" sta tentando di inniettare i processi
RATE_LIMIT_ALL_THREADS = u8"Rate limit tutti i thread del testo"; RATE_LIMIT_ALL_THREADS = u8"Rate limit tutti i thread del testo";
RATE_LIMIT_SELECTED_THREAD = u8"Rate limit thread del testo selezionato"; RATE_LIMIT_SELECTED_THREAD = u8"Rate limit thread del testo selezionato";
USE_TRANS_CACHE = u8"Utilizza la cache di traduzione"; USE_TRANS_CACHE = u8"Utilizza la cache di traduzione";
MAX_TRANSLATIONS_IN_TIMESPAN = u8"Numero di token del Rate Limit"; RATE_LIMIT_TOKEN_COUNT = u8"Numero di token del Rate Limit";
TIMESPAN = u8"Token del rate limit ripristina il ritardo (ms)"; RATE_LIMIT_TOKEN_RESTORE_DELAY = u8"Token del rate limit ripristina il ritardo (ms)";
TOO_MANY_TRANS_REQUESTS = L"Rate limit superato: rifiuta per fare altre richieste di traduzione"; TOO_MANY_TRANS_REQUESTS = L"Rate limit superato: rifiuta per fare altre richieste di traduzione";
TRANSLATION_ERROR = L"Errore durante la traduzione"; TRANSLATION_ERROR = L"Errore durante la traduzione";
USE_PREV_SENTENCE_CONTEXT = u8"Utilizza la precedente sentenza come contesto"; USE_PREV_SENTENCE_CONTEXT = u8"Utilizza la precedente sentenza come contesto";
API_KEY = u8"Chiave API"; API_KEY = u8"Chiave API";
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)";
SENTENCE_TOO_BIG = u8"Sentenza troppo grande da visualizzare";
MAX_SENTENCE_SIZE = u8"Dimensione massima sentenza"; MAX_SENTENCE_SIZE = u8"Dimensione massima sentenza";
TOPMOST = u8"Sempre in primo piano"; TOPMOST = u8"Sempre in primo piano";
DICTIONARY = u8"Dizionario"; DICTIONARY = u8"Dizionario";
@ -934,13 +882,7 @@ Questo file deve essere codificato in UTF-8.)";
SHOW_ORIGINAL = u8"Testo originale"; SHOW_ORIGINAL = u8"Testo originale";
SHOW_ORIGINAL_INFO = u8R"(Testo originale non sarà mostrato SHOW_ORIGINAL_INFO = u8R"(Testo originale non sarà mostrato
Funziona solo se questa estenzione è usata direttamente dopo un'estensione di traduzione)"; Funziona solo se questa estenzione è usata direttamente dopo un'estensione di traduzione)";
ORIGINAL_AFTER_TRANSLATION = u8"Mostra testo originale dopo traduzione";
SIZE_LOCK = u8"Lock delle dimensione"; SIZE_LOCK = u8"Lock delle dimensione";
POSITION_LOCK = u8"Lock delle posizione";
CENTERED_TEXT = u8"Testo centrato";
AUTO_RESIZE_WINDOW_HEIGHT = u8"Auto resize altezza finestra";
CLICK_THROUGH = u8"Clicca attraverso\tAlt+X";
HIDE_MOUSEOVER = u8"Nascondi testo mouseover";
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";
@ -963,7 +905,7 @@ Modifiche alle variabili globali da ProcessSentence non sono garantite di persis
Proprietà in sentenceInfo: Proprietà in sentenceInfo:
"current select": 0 a meno che la sentenza è nel thread di testo attualmente scelto dall'utente. "current select": 0 a meno che la sentenza è nel thread di testo attualmente scelto dall'utente.
"process id": ID del processo che da cui proviene la sentenza. 0 per console e per appunti. "process id": id del processo che da cui proviene la sentenza. 0 per console e per appunti.
"text number": numero dell'attuale thread di testo. Conta uno ad uno quando i thread di testo sono creati. 0 per console, 1 per appunti. "text number": numero dell'attuale thread di testo. Conta uno ad uno quando i thread di testo sono creati. 0 per console, 1 per appunti.
--]] --]]
function ProcessSentence(sentence, sentenceInfo) function ProcessSentence(sentence, sentenceInfo)
@ -980,17 +922,6 @@ I comandi di rimpiazzo devono essere formattati cosi:
Tutto il testo in questo file all'infuori di un comando di rimpiazzo è ignorato. Tutto il testo in questo file all'infuori di un comando di rimpiazzo è ignorato.
La spaziatura nel testo_originale è ignorato, ma testo_sostituito può contenere spaziature, ritorni a capo, ecc. La spaziatura nel testo_originale è ignorato, ma testo_sostituito può contenere spaziature, ritorni a capo, ecc.
Questo file deve essere codificato in Unicode (UTF-16 Little Endian).)"; Questo file deve essere codificato in Unicode (UTF-16 Little Endian).)";
REGEX_REPLACER_INSTRUCTIONS = LR"(Questo file fa qualcosa solo quando l'estenzione "Regex Replacer" è utilizzata.
I comandi di sostituzione devono essere formattati cosi:
|REGEX|espressione_regolare|BECOMES|testo_sostituito|MODIFIER|modificatori|END|
Il parametro "MODIFIER" può contenere i seguenti modificatori:
"g" la sostituzione è globale.
"i" la sostituzione ignora maiuscole/minuscole.
Se il modificatore è vuoto, la sostituzione viene applicata alla sola prima corrispondenza
e fa distinzione tra maiuscole e minuscole.
Tutto il testo in questo file all'infuori di un comando di sostituzione è ignorato.
Questo file deve essere codificato in Unicode (UTF-16 Little Endian).
Apprendere, creare e testare Espressioni Regolari: https://regexr.com/)";
THREAD_LINKER = u8"Collegatore di thread"; THREAD_LINKER = u8"Collegatore di thread";
LINK = u8"Collegamento"; LINK = u8"Collegamento";
THREAD_LINK_FROM = u8"Numero di thread da cui collegarsi"; THREAD_LINK_FROM = u8"Numero di thread da cui collegarsi";
@ -1030,7 +961,7 @@ Pressione delete com uma extensão selecionada para removê-la.)";
CLIPBOARD = L"Área de Transferência"; CLIPBOARD = L"Área de Transferência";
ABOUT = L"Textractor " ARCH L" v" VERSION LR"( Feito por mim: Artikash (e-mail: akashmozumdar@gmail.com) ABOUT = L"Textractor " ARCH L" v" VERSION LR"( Feito por mim: Artikash (e-mail: akashmozumdar@gmail.com)
Homepage do Projeto: https://github.com/Artikash/Textractor Homepage do Projeto: https://github.com/Artikash/Textractor
Vídeo Tutorial: https://github.com/Artikash/Textractor/blob/master/docs/TUTORIAL.md Vídeo Tutorial: https://tinyurl.com/textractor-tutorial
Por favor, em caso de problemas, requisição de recurso e/ou funções e de dúvidas, entrar em contato comigo. Use o Inglês para se comunicar. Por favor, em caso de problemas, requisição de recurso e/ou funções e de dúvidas, entrar em contato comigo. Use o Inglês para se comunicar.
Você pode fazê-lo por meio da Homepage do Projeto (na aba "Issues") ou via E-mail. Você pode fazê-lo por meio da Homepage do Projeto (na aba "Issues") ou via E-mail.
O código-fonte se encontra disponível na Homepage do projeto sob a licença GPLv3. O código-fonte se encontra disponível na Homepage do projeto sob a licença GPLv3.
@ -1107,7 +1038,7 @@ Esse arquívo deve ser codifícado em (UTF-16 little endian).)";
CLIPBOARD = L"ข้อมูลชั่วคราว"; CLIPBOARD = L"ข้อมูลชั่วคราว";
ABOUT = L"Textractor " ARCH L" v" VERSION LR"( ได้ถูกพัฒนาโดย: Artikash (email: akashmozumdar@gmail.com) ABOUT = L"Textractor " ARCH L" v" VERSION LR"( ได้ถูกพัฒนาโดย: Artikash (email: akashmozumdar@gmail.com)
: https://github.com/Artikash/Textractor : https://github.com/Artikash/Textractor
: https://github.com/Artikash/Textractor/blob/master/docs/TUTORIAL.md : https://tinyurl.com/textractor-tutorial
Textractor Textractor
Issue Issue
Source code GPLv3 )"; Source code GPLv3 )";
@ -1157,7 +1088,7 @@ Source code สามารถหาได้จากส่วนของ GPLv
SETTINGS = u8"설정"; SETTINGS = u8"설정";
EXTENSIONS = u8"확장기능"; EXTENSIONS = u8"확장기능";
SELECT_PROCESS = u8"프로세스 선택"; SELECT_PROCESS = u8"프로세스 선택";
ATTACH_INFO = u8R"(부착하려는 게임이 보이지 않는다면, 관리자 권한으로 실행해보세요. 프로세스 ID를 입력 할 수도 있습니다.)"; ATTACH_INFO = u8R"(부착하려는 게임이 보이지 않는다면, 관리자 권한으로 실행해보세요. 프로세스 id를 입력 할 수도 있습니다.)";
SELECT_PROCESS_INFO = u8"직접 프로세스파일 이름을 타이핑한다면, 정확한 경로를 입력하세요"; SELECT_PROCESS_INFO = u8"직접 프로세스파일 이름을 타이핑한다면, 정확한 경로를 입력하세요";
FROM_COMPUTER = u8"컴퓨터로부터 선택"; FROM_COMPUTER = u8"컴퓨터로부터 선택";
PROCESSES = u8"프로세스 (*.exe)"; PROCESSES = u8"프로세스 (*.exe)";
@ -1301,14 +1232,14 @@ Pour supprimer une extension, sélectionnez-la et appuyez sur supprimer)";
CLIPBOARD = L"Presse-papier"; CLIPBOARD = L"Presse-papier";
ABOUT = L"Textractor " ARCH L" v" VERSION LR"( Fait par moi: Artikash (email: akashmozumdar@gmail.com) ABOUT = L"Textractor " ARCH L" v" VERSION LR"( Fait par moi: Artikash (email: akashmozumdar@gmail.com)
Page d'acceuil du projet: https://github.com/Artikash/Textractor Page d'acceuil du projet: https://github.com/Artikash/Textractor
Vidéo tuto: https://github.com/Artikash/Textractor/blob/master/docs/TUTORIAL.md Vidéo tuto: https://tinyurl.com/textractor-tutorial
FAQ: https://github.com/Artikash/Textractor/wiki/FAQ FAQ: https://github.com/Artikash/Textractor/wiki/FAQ
Veuillez me contacter pour tout problème, demande de fonctionnalité ou question concernant Textractor Veuillez me contacter pour tout problème, demande de fonctionnalité ou question concernant Textractor
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 [-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é";
NEED_32_BIT = L"Textractor: incompatibilité d'architecture: seul Textractor x86 peut injecter ce processus"; NEED_32_BIT = L"Textractor: incompatibilité d'architecture: seul Textractor x86 peut injecter ce processus";
@ -1340,14 +1271,15 @@ example: Textractor -p4466 -p"My Game.exe" tries to inject processes with ID 446
RATE_LIMIT_ALL_THREADS = u8"Taux limite tout les threads de texte"; RATE_LIMIT_ALL_THREADS = u8"Taux limite tout les threads de texte";
RATE_LIMIT_SELECTED_THREAD = u8"Limite de débit du thread de texte sélectionné"; RATE_LIMIT_SELECTED_THREAD = u8"Limite de débit du thread de texte sélectionné";
USE_TRANS_CACHE = u8"Utiliser le cache de traduction"; USE_TRANS_CACHE = u8"Utiliser le cache de traduction";
MAX_TRANSLATIONS_IN_TIMESPAN = u8"Nombre de tokens du limiteur de débit"; RATE_LIMIT_TOKEN_COUNT = u8"Nombre de tokens du limiteur de débit";
TIMESPAN = u8"Délai de restauration du token du limiteur de débit (ms)"; RATE_LIMIT_TOKEN_RESTORE_DELAY = u8"Délai de restauration du token du limiteur de débit (ms)";
TOO_MANY_TRANS_REQUESTS = L"Limite de taux dépassée: refus de faire plus de demande de traduction"; TOO_MANY_TRANS_REQUESTS = L"Limite de taux dépassée: refus de faire plus de demande de traduction";
TRANSLATION_ERROR = L"Une erreur est survenue pendant la traduction"; TRANSLATION_ERROR = L"Une erreur est survenue pendant la traduction";
USE_PREV_SENTENCE_CONTEXT = u8"Utiliser la phrase précédente comme contexte"; USE_PREV_SENTENCE_CONTEXT = u8"Utiliser la phrase précédente comme contexte";
API_KEY = u8"API key"; API_KEY = u8"API key";
EXTRA_WINDOW_INFO = u8R"(Clic droit pour modifier les paramètres EXTRA_WINDOW_INFO = u8R"(Clic droit pour modifier les paramètres
Cliquez et faites glisser sur les bords de la fenêtre pour vous déplacer ou dans le coin inférieur droit pour redimensionner)"; Cliquez et faites glisser sur les bords de la fenêtre pour vous déplacer ou dans le coin inférieur droit pour redimensionner)";
SENTENCE_TOO_BIG = u8"Phrase trop grande pour être affichée";
MAX_SENTENCE_SIZE = u8"Taille maximale de la phrase"; MAX_SENTENCE_SIZE = u8"Taille maximale de la phrase";
TOPMOST = u8"Toujours au dessus"; TOPMOST = u8"Toujours au dessus";
DICTIONARY = u8"Dictionnaire"; DICTIONARY = u8"Dictionnaire";
@ -1388,7 +1320,7 @@ Cette extension utilise plusieurs copies de l'interpréteur Lua pour la sécurit
Les modifications apportées aux variables globales à partir de ProcessSentence ne sont pas garanties de persister. Les modifications apportées aux variables globales à partir de ProcessSentence ne sont pas garanties de persister.
Properties in sentenceInfo: Properties in sentenceInfo:
"current select": 0 unless sentence is in the text thread currently selected by the user. "current select": 0 unless sentence is in the text thread currently selected by the user.
"process id": process ID that the sentence is coming from. 0 for console and clipboard. "process id": process id that the sentence is coming from. 0 for console and clipboard.
"text number": number of the current text thread. Counts up one by one as text threads are created. 0 for console, 1 for clipboard. "text number": number of the current text thread. Counts up one by one as text threads are created. 0 for console, 1 for clipboard.
--]] --]]
function ProcessSentence(sentence, sentenceInfo) function ProcessSentence(sentence, sentenceInfo)

View File

@ -1,51 +1,32 @@
include_directories(. util minhook/include) include_directories(. util)
if(${CMAKE_SIZEOF_VOID_P} EQUAL 8) if(${CMAKE_SIZEOF_VOID_P} EQUAL 8)
set(minhook_src set(texthook_src
minhook/src/buffer.c main.cc
minhook/src/hook.c texthook.cc
minhook/src/trampoline.c hookfinder.cc
minhook/src/hde/hde64.c engine/match.cc
) engine/match64.cc
set(texthook_src engine/native/pchooks.cc
main.cc util/ithsys/ithsys.cc
texthook.cc util/util.cc
hookfinder.cc )
engine/match.cc
engine/match64.cc
engine/native/pchooks.cc
util/ithsys/ithsys.cc
util/util.cc
)
else() else()
set(minhook_src set(texthook_src
minhook/src/buffer.c main.cc
minhook/src/hook.c texthook.cc
minhook/src/trampoline.c hookfinder.cc
minhook/src/hde/hde32.c engine/engine.cc
) engine/match.cc
set(texthook_src engine/match32.cc
main.cc engine/native/pchooks.cc
texthook.cc util/util.cc
hookfinder.cc util/ithsys/ithsys.cc
engine/engine.cc util/disasm/disasm.cc
engine/match.cc util/memdbg/memsearch.cc
engine/match32.cc )
engine/native/pchooks.cc
util/util.cc
util/ithsys/ithsys.cc
util/disasm/disasm.cc
util/memdbg/memsearch.cc
)
endif() endif()
add_library(minhook ${minhook_src})
add_library(texthook MODULE ${texthook_src}) add_library(texthook MODULE ${texthook_src})
# isn't there a better way to do this? target_precompile_headers(texthook REUSE_FROM pch)
target_precompile_headers(texthook PRIVATE ../include/common.h)
if(NOT CMAKE_BUILD_TYPE MATCHES Debug)
target_compile_options(minhook PRIVATE /MT)
target_compile_options(texthook PRIVATE /MT)
target_link_options(texthook PRIVATE /NODEFAULTLIB:MSVCRT)
endif()
target_link_libraries(texthook minhook) target_link_libraries(texthook minhook)

View File

@ -4494,8 +4494,7 @@ bool InsertRUGP1Hook()
*/ */
bool InsertRUGP2Hook() bool InsertRUGP2Hook()
{ {
auto module = GetModuleHandleW(L"vm60.dll"); if (!Util::CheckFile(L"vm60.dll") /*|| !SafeFillRange(L"vm60.dll", &low, &high)*/) {
if (!module /*|| !SafeFillRange(L"vm60.dll", &low, &high)*/) {
ConsoleOutput("vnreng:rUGP2: vm60.dll does not exist"); ConsoleOutput("vnreng:rUGP2: vm60.dll does not exist");
return false; return false;
} }
@ -4509,7 +4508,7 @@ bool InsertRUGP2Hook()
0x89,0x75, 0x0c // 1001e527 8975 0c mov dword ptr ss:[ebp+0xc],esi 0x89,0x75, 0x0c // 1001e527 8975 0c mov dword ptr ss:[ebp+0xc],esi
}; };
enum { addr_offset = 0x1001e51d - 0x1001e515 }; enum { addr_offset = 0x1001e51d - 0x1001e515 };
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), (DWORD)module, Util::QueryModuleLimits(module).second); ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress);
//GROWL_DWORD(addr); //GROWL_DWORD(addr);
if (!addr) { if (!addr) {
ConsoleOutput("vnreng:rUGP2: pattern not found"); ConsoleOutput("vnreng:rUGP2: pattern not found");
@ -4632,15 +4631,20 @@ static void InsertAliceHook2(DWORD addr)
// jichi 5/13/2015: Looking for function entries in StoatSpriteEngine.dll // jichi 5/13/2015: Looking for function entries in StoatSpriteEngine.dll
bool InsertAliceHook() bool InsertAliceHook()
{ {
if (auto addr = Util::FindFunction("SP_TextDraw")) { DWORD addr;
if (addr = (DWORD)GetProcAddress(GetModuleHandleW(L"SACT2.dll"), "SP_TextDraw")) {
InsertAliceHook1(addr); InsertAliceHook1(addr);
return true; return true;
} }
if (addr = (DWORD)GetProcAddress(GetModuleHandleW(L"SACTDX.dll"), "SP_TextDraw")) {
InsertAliceHook1(addr);
return true;
}
//if (GetFunctionAddr("SP_SetTextSprite", &addr, &low, &high, 0) && addr) { //if (GetFunctionAddr("SP_SetTextSprite", &addr, &low, &high, 0) && addr) {
// InsertAliceHook2(addr); // InsertAliceHook2(addr);
// return true; // return true;
//} //}
if (auto addr = Util::FindFunction("SP_SetTextSprite")) { // Artikash 6/27/2018 not sure if this works if (addr = (DWORD)GetProcAddress(GetModuleHandleW(L"StoatSpriteEngine.dll"), "SP_SetTextSprite")) { // Artikash 6/27/2018 not sure if this works
InsertAliceHook2(addr); InsertAliceHook2(addr);
return true; return true;
} }
@ -5730,21 +5734,21 @@ void SpecialHookShina2(DWORD esp_base, HookParam *, BYTE, DWORD *data, DWORD *sp
// Used to merge correct text thread. // Used to merge correct text thread.
// 1. Only keep threads with 0 and -1 split // 1. Only keep threads with 0 and -1 split
// 2. Skip the thread withb 0 split and with minimum return address // 2. Skip the thread withb 0 split and with minimum return address
//void SpecialHookShina1(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len) void SpecialHookShina1(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len)
//{ {
// static DWORD min_retaddr = -1; static DWORD min_retaddr = -1;
// DWORD s = *(DWORD *)(esp_base + hp->split); DWORD s = *(DWORD *)(esp_base + hp->split);
// if (s == 0 || (s & 0xffff) == 0xffff) { // only keep threads with 0 and -1 split if (s == 0 || (s & 0xffff) == 0xffff) { // only keep threads with 0 and -1 split
// if (s == 0 && retof(esp_base) <= min_retaddr) { if (s == 0 && retof(esp_base) <= min_retaddr) {
// min_retaddr = retof(esp_base); min_retaddr = retof(esp_base);
// return; return;
// } }
// *split = FIXED_SPLIT_VALUE; *split = FIXED_SPLIT_VALUE;
// // Follow the same logic as the hook. // Follow the same logic as the hook.
// *data = *(DWORD *)*data; // DATA_INDIRECT *data = *(DWORD *)*data; // DATA_INDIRECT
// *len = LeadByteTable[*data & 0xff]; *len = LeadByteTable[*data & 0xff];
// } }
//} }
// jichi 8/27/2013 // jichi 8/27/2013
// Return ShinaRio version number // Return ShinaRio version number
@ -5803,7 +5807,7 @@ bool InsertShinaHook()
trigger_fun = [](LPVOID funcAddr, DWORD, DWORD stack) trigger_fun = [](LPVOID funcAddr, DWORD, DWORD stack)
{ {
bool ret = false; bool ret = false;
if (funcAddr != GetGlyphOutlineA && funcAddr != GetTextExtentPoint32A) return false; if (funcAddr != GetGlyphOutlineA) return false;
for (int i = 0; i < 100; ++i) for (int i = 0; i < 100; ++i)
{ {
// Address of text is somewhere on stack in call to func. Search for it. // Address of text is somewhere on stack in call to func. Search for it.
@ -5817,7 +5821,7 @@ bool InsertShinaHook()
hp.type = DIRECT_READ; hp.type = DIRECT_READ;
hp.address = addr; hp.address = addr;
ConsoleOutput("Textractor: triggered: adding dynamic reader"); ConsoleOutput("Textractor: triggered: adding dynamic reader");
NewHook(hp, "ShinaRio READ"); NewHook(hp, "READ");
ret = true; ret = true;
} }
}; };
@ -5825,8 +5829,9 @@ bool InsertShinaHook()
return ret; return ret;
}; };
ConsoleOutput("Textractor: ShinaRio 2.50+: adding trigger"); ConsoleOutput("Textractor: ShinaRio 2.50+: adding trigger");
return true;
} }
if (ver >= 48) { // v2.48, v2.49 else if (ver >= 48) { // v2.48, v2.49
HookParam hp = {}; HookParam hp = {};
hp.address = (DWORD)::GetTextExtentPoint32A; hp.address = (DWORD)::GetTextExtentPoint32A;
hp.text_fun = SpecialHookShina2; hp.text_fun = SpecialHookShina2;
@ -5983,7 +5988,7 @@ bool InsertWaffleHook()
{ {
bool found = false; bool found = false;
for (DWORD i = processStartAddress + 0x1000; i < processStopAddress - 4; i++) for (DWORD i = processStartAddress + 0x1000; i < processStopAddress - 4; i++)
if (*(DWORD *)i == 0xac68 && *(BYTE*)(i + 4) == 0) { if (*(DWORD *)i == 0xac68) {
HookParam hp = {}; HookParam hp = {};
hp.address = i; hp.address = i;
hp.length_offset = 1; hp.length_offset = 1;
@ -10774,59 +10779,8 @@ bool InsertArtemis2Hook()
return true; return true;
} }
bool InsertArtemis3Hook()
{
const BYTE bytes[] = {
0x55, // 005FD780 | 55 | push ebp |
0x8B, 0xEC, // 005FD781 | 8BEC | mov ebp,esp |
0x83, 0xE4, 0xF8, // 005FD783 | 83E4 F8 | and esp,FFFFFFF8 |
0x83, 0xEC, 0x3C, // 005FD786 | 83EC 3C | sub esp,3C |
0xA1, XX4, // 005FD789 | A1 6C908600 | mov eax,dword ptr ds:[86906C] |
0x33, 0xC4, // 005FD78E | 33C4 | xor eax,esp |
0x89, 0x44, 0x24, 0x38, // 005FD790 | 894424 38 | mov dword ptr ss:[esp+38],eax |
0x53, // 005FD794 | 53 | push ebx |
0x56, // 005FD795 | 56 | push esi |
0x8B, 0xC1, // 005FD796 | 8BC1 | mov eax,ecx |
0xC7, 0x44, 0x24, 0x14, 0x00, 0x00, 0x00, 0x00, // 005FD798 | C74424 14 00000000 | mov dword ptr ss:[esp+14],0 |
0x8B, 0x4D, 0x0C, // 005FD7A0 | 8B4D 0C | mov ecx,dword ptr ss:[ebp+C] |
0x33, 0xF6, // 005FD7A3 | 33F6 | xor esi,esi |
0x57, // 005FD7A5 | 57 | push edi |
0x8B, 0x7D, 0x08, // 005FD7A6 | 8B7D 08 | mov edi,dword ptr ss:[ebp+8] |
0x89, 0x44, 0x24, 0x14, // 005FD7A9 | 894424 14 | mov dword ptr ss:[esp+14],eax |
0x89, 0x4C, 0x24, 0x28, // 005FD7AD | 894C24 28 | mov dword ptr ss:[esp+28],ecx |
0x80, 0x3F, 0x00, // 005FD7B1 | 803F 00 | cmp byte ptr ds:[edi],0 |
0x0F, 0x84, XX4, // 005FD7B4 | 0F84 88040000 | je ヘンタイ・プリズンsplit 1.5FDC42 |
0x83, 0xB8, XX4, 0x00, // 005FD7BA | 83B8 74030000 00 | cmp dword ptr ds:[eax+374],0 |
0x8B, 0xDF, // 005FD7C1 | 8BDF | mov ebx,edi |
};
enum { addr_offset = 0 }; // distance to the beginning of the function, which is 0x55 (push ebp)
ULONG range = min(processStopAddress - processStartAddress, MAX_REL_ADDR);
ULONG addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStartAddress + range);
if (!addr) {
ConsoleOutput("Textractor:Artemis3: pattern not found");
return false;
}
addr += addr_offset;
enum { push_ebp = 0x55 }; // beginning of the function
if (*(BYTE *)addr != push_ebp) {
ConsoleOutput("Textractor:Artemis3: beginning of the function not found");
return false;
}
HookParam hp = {};
hp.address = addr;
hp.offset = pusha_ebx_off - 4;
hp.type = USING_UTF8;
ConsoleOutput("Textractor: INSERT Artemis3");
NewHook(hp, "Artemis3");
return true;
}
bool InsertArtemisHook() bool InsertArtemisHook()
{ return InsertArtemis1Hook() || InsertArtemis2Hook() || InsertArtemis3Hook(); } { return InsertArtemis1Hook() || InsertArtemis2Hook(); }
/** /**
* jichi 1/2/2014: Taskforce2 Engine * jichi 1/2/2014: Taskforce2 Engine
@ -14061,9 +14015,6 @@ bool InsertHorkEyeHook()
return true; return true;
} }
memcpy(spDefault.pattern, Array<BYTE>{ 0xcc, 0xcc, 0xcc, XX, 0xec }, spDefault.length = 5);
spDefault.offset = 3;
const BYTE bytes2[] = const BYTE bytes2[] =
{ {
0x83, 0xec, XX, // sub esp,?? 0x83, 0xec, XX, // sub esp,??
@ -16965,14 +16916,8 @@ bool InsertRenpyHook()
hp.offset = 4; hp.offset = 4;
hp.index = 0xc; hp.index = 0xc;
hp.length_offset = 0; hp.length_offset = 0;
//hp.split = pusha_ebx_off - 4; hp.split = pusha_ebx_off - 4;
hp.text_fun = [](auto, auto, auto, DWORD* data, DWORD* split, DWORD* count) hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT | USING_SPLIT;
{
*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'%'; }; //hp.filter_fun = [](void* str, auto, auto, auto) { return *(wchar_t*)str != L'%'; };
NewHook(hp, "Ren'py"); NewHook(hp, "Ren'py");
return true; return true;
@ -17001,7 +16946,7 @@ void InsertMonoHook(HMODULE h)
if (!getDomain || !getName || !getJitInfo) goto failed; if (!getDomain || !getName || !getJitInfo) goto failed;
static auto domain = getDomain(); static auto domain = getDomain();
if (!domain) goto failed; if (!domain) goto failed;
ConsoleOutput("Textractor: Mono Dynamic ENTER (hooks = %s)", *loadedConfig ? loadedConfig : "brute force"); ConsoleOutput("Textractor: Mono Dynamic ENTER (hooks = %s)", loadedConfig ? loadedConfig : "brute force");
const BYTE prolog[] = { 0x55, 0x8b, 0xec }; const BYTE prolog[] = { 0x55, 0x8b, 0xec };
for (auto addr : Util::SearchMemory(prolog, sizeof(prolog), PAGE_EXECUTE_READWRITE)) for (auto addr : Util::SearchMemory(prolog, sizeof(prolog), PAGE_EXECUTE_READWRITE))
{ {
@ -17016,7 +16961,7 @@ void InsertMonoHook(HMODULE h)
HookParam hp = {}; HookParam hp = {};
hp.address = addr; hp.address = addr;
hp.type = USING_UNICODE | FULL_STRING; hp.type = USING_UNICODE | FULL_STRING;
if (!*loadedConfig) hp.type |= KNOWN_UNSTABLE; if (!loadedConfig) hp.type |= KNOWN_UNSTABLE;
hp.offset = 4; hp.offset = 4;
char nameForUser[HOOK_NAME_SIZE] = {}; char nameForUser[HOOK_NAME_SIZE] = {};
strncpy_s(nameForUser, name + 1, HOOK_NAME_SIZE - 1); strncpy_s(nameForUser, name + 1, HOOK_NAME_SIZE - 1);
@ -17034,7 +16979,7 @@ void InsertMonoHook(HMODULE h)
__except (EXCEPTION_EXECUTE_HANDLER) {} __except (EXCEPTION_EXECUTE_HANDLER) {}
}(addr); }(addr);
} }
if (!*loadedConfig) ConsoleOutput("Textractor: Mono Dynamic used brute force: if performance issues arise, please specify the correct hook in the game configuration"); if (!loadedConfig) ConsoleOutput("Textractor: Mono Dynamic used brute force: if performance issues arise, please specify the correct hook in the game configuration");
return true; return true;
failed: failed:
ConsoleOutput("Textractor: Mono Dynamic failed"); ConsoleOutput("Textractor: Mono Dynamic failed");
@ -17091,7 +17036,7 @@ bool InsertMonoHooks()
if (FARPROC addr = ::GetProcAddress(h, func.functionName)) { if (FARPROC addr = ::GetProcAddress(h, func.functionName)) {
hp.address = (DWORD)addr; hp.address = (DWORD)addr;
hp.type = func.hookType; hp.type = func.hookType;
if (*loadedConfig) hp.type |= HOOK_EMPTY; if (loadedConfig) hp.type |= HOOK_EMPTY;
hp.filter_fun = NoAsciiFilter; hp.filter_fun = NoAsciiFilter;
hp.offset = func.textIndex * 4; hp.offset = func.textIndex * 4;
hp.length_offset = func.lengthIndex * 4; hp.length_offset = func.lengthIndex * 4;

View File

@ -14,7 +14,7 @@ namespace Engine {
// Global variables // Global variables
extern wchar_t *processName, // cached extern wchar_t *processName, // cached
processPath[MAX_PATH]; // cached processPath[MAX_PATH]; // cached
inline const char *requestedEngine = "", * loadedConfig = ""; inline const char *requestedEngine = nullptr, *loadedConfig = nullptr;
bool InsertMonoHooks(); // Mono bool InsertMonoHooks(); // Mono

View File

@ -45,13 +45,13 @@ namespace Engine
if (AutoHandle<> configFile = CreateFileW(configFilename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) if (AutoHandle<> configFile = CreateFileW(configFilename, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL))
{ {
ReadFile(configFile, configFileData, sizeof(configFileData) - 1, DUMMY, nullptr); ReadFile(configFile, configFileData, sizeof(configFileData) - 1, DUMMY, nullptr);
if (strnicmp(configFileData, "engine:", 7) == 0) if (strncmp(configFileData, "Engine:", 7) == 0)
{ {
if (const char* config = strchr(configFileData, '\n')) *(char*)(loadedConfig = config)++ = 0; if (loadedConfig = strchr(configFileData, '\n')) *(char*)loadedConfig++ = 0;
ConsoleOutput("Textractor: Engine = %s", requestedEngine = strlwr(configFileData + 7)); ConsoleOutput("Textractor: Engine = %s", requestedEngine = configFileData + 7);
} }
else loadedConfig = configFileData; else loadedConfig = configFileData;
if (!*loadedConfig || strstr(configFileData, "https://")) loadedConfig = ""; if ((loadedConfig && !*loadedConfig) || strstr(configFileData, "https://")) loadedConfig = nullptr;
else ConsoleOutput("Textractor: game configuration loaded"); else ConsoleOutput("Textractor: game configuration loaded");
} }
@ -67,14 +67,14 @@ namespace Engine
spDefault.maxAddress = processStopAddress; spDefault.maxAddress = processStopAddress;
ConsoleOutput("Textractor: hijacking process located from 0x%p to 0x%p", processStartAddress, processStopAddress); ConsoleOutput("Textractor: hijacking process located from 0x%p to 0x%p", processStartAddress, processStopAddress);
if (!strstr(requestedEngine, "none")) DetermineEngineType(); DetermineEngineType();
if (processStartAddress + 0x40000 > processStopAddress) ConsoleOutput("Textractor: WARNING injected process is very small, possibly a dummy!"); if (processStartAddress + 0x40000 > processStopAddress) ConsoleOutput("Textractor: WARNING injected process is very small, possibly a dummy!");
}(), 0); }(), 0);
} }
bool ShouldMonoHook(const char* name) bool ShouldMonoHook(const char* name)
{ {
if (!*loadedConfig) return strstr(name, "string:") && !strstr(name, "string:mem"); if (!loadedConfig) return strstr(name, "string:") && !strstr(name, "string:mem");
for (const char* hook = loadedConfig; hook; hook = strchr(hook + 1, '\t')) for (const char* hook = loadedConfig; hook; hook = strchr(hook + 1, '\t'))
for (auto start = name; *start; ++start) for (auto start = name; *start; ++start)
for (int i = 0; ; ++i) for (int i = 0; ; ++i)

View File

@ -629,9 +629,10 @@ bool DetermineEngineAtLast()
} }
if (Util::CheckFile(L"MovieTexture.dll") && (InsertPensilHook() || Insert2RMHook())) // MovieTexture.dll also exists in 2RM games such as 母子愛2体験版, which is checked first if (Util::CheckFile(L"MovieTexture.dll") && (InsertPensilHook() || Insert2RMHook())) // MovieTexture.dll also exists in 2RM games such as 母子愛2体験版, which is checked first
return true; return true;
if ((Util::CheckFile(L"system") && Util::CheckFile(L"system.dat")) || Util::CheckFile(L"*01")) // jichi 7/31/2015 & Artikash 6/15/2018 if ((Util::CheckFile(L"system") && Util::CheckFile(L"system.dat")) || Util::CheckFile(L"*01")) { // jichi 7/31/2015 & Artikash 6/15/2018
if (InsertAbelHook()) InsertAbelHook();
return true; return true;
}
if (Util::CheckFile(L"data\\*.cpk")) { // jichi 12/2/2014 if (Util::CheckFile(L"data\\*.cpk")) { // jichi 12/2/2014
Insert5pbHook(); Insert5pbHook();
return true; return true;

View File

@ -80,7 +80,7 @@ namespace Engine
if (!getDomain || !getName || !getJitInfo) goto failed; if (!getDomain || !getName || !getJitInfo) goto failed;
static auto domain = getDomain(); static auto domain = getDomain();
if (!domain) goto failed; if (!domain) goto failed;
ConsoleOutput("Textractor: Mono Dynamic ENTER (hooks = %s)", *loadedConfig ? loadedConfig : "brute force"); ConsoleOutput("Textractor: Mono Dynamic ENTER (hooks = %s)", loadedConfig ? loadedConfig : "brute force");
const BYTE prolog1[] = { 0x55, 0x48, 0x8b, 0xec }; const BYTE prolog1[] = { 0x55, 0x48, 0x8b, 0xec };
const BYTE prolog2[] = { 0x48, 0x83, 0xec }; const BYTE prolog2[] = { 0x48, 0x83, 0xec };
for (auto [prolog, size] : Array<const BYTE*, size_t>{ { prolog1, sizeof(prolog1) }, { prolog2, sizeof(prolog2) } }) for (auto [prolog, size] : Array<const BYTE*, size_t>{ { prolog1, sizeof(prolog1) }, { prolog2, sizeof(prolog2) } })
@ -97,7 +97,7 @@ namespace Engine
HookParam hp = {}; HookParam hp = {};
hp.address = addr; hp.address = addr;
hp.type = USING_STRING | USING_UNICODE | FULL_STRING; hp.type = USING_STRING | USING_UNICODE | FULL_STRING;
if (!*loadedConfig) hp.type |= KNOWN_UNSTABLE; if (!loadedConfig) hp.type |= KNOWN_UNSTABLE;
hp.offset = -0x20; // rcx hp.offset = -0x20; // rcx
hp.padding = 20; hp.padding = 20;
char nameForUser[HOOK_NAME_SIZE] = {}; char nameForUser[HOOK_NAME_SIZE] = {};
@ -119,7 +119,7 @@ namespace Engine
}(addr); }(addr);
} }
if (!*loadedConfig) ConsoleOutput("Textractor: Mono Dynamic used brute force: if performance issues arise, please specify the correct hook in the game configuration"); if (!loadedConfig) ConsoleOutput("Textractor: Mono Dynamic used brute force: if performance issues arise, please specify the correct hook in the game configuration");
return true; return true;
failed: failed:
ConsoleOutput("Textractor: Mono Dynamic failed"); ConsoleOutput("Textractor: Mono Dynamic failed");
@ -132,44 +132,26 @@ namespace Engine
// sample game https://www.freem.ne.jp/dl/win/18963 // sample game https://www.freem.ne.jp/dl/win/18963
bool InsertV8Hook(HMODULE module) bool InsertV8Hook(HMODULE module)
{ {
auto getV8Length = [](uintptr_t, uintptr_t data) if (uint64_t addr = (uint64_t)GetProcAddress(module, "?Write@String@v8@@QEBAHPEAGHHH@Z"))
{
int len = *(int*)(data - 4);
return len > 0 && len < PIPE_BUFFER_SIZE ? len * 2 : 0;
};
uint64_t addr1 = (uint64_t)GetProcAddress(module, "?Write@String@v8@@QEBAHPEAGHHH@Z"),
// Artikash 6/7/2021: Add new hook for new version of V8 used by RPG Maker MZ
addr2 = (uint64_t)GetProcAddress(module, "??$WriteToFlat@G@String@internal@v8@@SAXV012@PEAGHH@Z");
if (addr1 || addr2)
{ {
std::tie(spDefault.minAddress, spDefault.maxAddress) = Util::QueryModuleLimits(module); std::tie(spDefault.minAddress, spDefault.maxAddress) = Util::QueryModuleLimits(module);
spDefault.maxRecords = Util::SearchMemory(spDefault.pattern, spDefault.length, PAGE_EXECUTE, spDefault.minAddress, spDefault.maxAddress).size() * 20; spDefault.maxRecords = Util::SearchMemory(spDefault.pattern, spDefault.length, PAGE_EXECUTE, spDefault.minAddress, spDefault.maxAddress).size() * 20;
ConsoleOutput("Textractor: JavaScript hook is known to be low quality: try searching for hooks if you don't like it"); ConsoleOutput("Textractor: JavaScript hook is known to be low quality: try searching for hooks if you don't like it");
}
if (addr1)
{
HookParam hp = {}; HookParam hp = {};
hp.type = USING_STRING | USING_UNICODE | DATA_INDIRECT; hp.type = USING_STRING | USING_UNICODE | DATA_INDIRECT;
hp.address = addr1; hp.address = addr;
hp.offset = -0x20; // rcx hp.offset = -0x20; // rcx
hp.index = 0; hp.index = 0;
hp.padding = 23; hp.padding = 23;
hp.length_fun = getV8Length; hp.length_fun = [](uintptr_t, uintptr_t data)
{
int len = *(int*)(data - 4);
return len > 0 && len < PIPE_BUFFER_SIZE ? len * 2 : 0;
};
NewHook(hp, "JavaScript"); NewHook(hp, "JavaScript");
return true;
} }
if (addr2) return false;
{
HookParam hp = {};
hp.type = USING_STRING | USING_UNICODE;
hp.address = addr2;
hp.offset = -0x20; // rcx
hp.padding = 11;
hp.length_fun = getV8Length;
NewHook(hp, "JavaScript");
}
return addr1 || addr2;
} }
/** Artikash 8/10/2018: Ren'py /** Artikash 8/10/2018: Ren'py

View File

@ -2,7 +2,6 @@
#include "defs.h" #include "defs.h"
#include "main.h" #include "main.h"
#include "util.h" #include "util.h"
#include "MinHook.h"
extern const char* HOOK_SEARCH_STARTING; extern const char* HOOK_SEARCH_STARTING;
extern const char* HOOK_SEARCH_INITIALIZING; extern const char* HOOK_SEARCH_INITIALIZING;

View File

@ -9,7 +9,6 @@
#include "texthook.h" #include "texthook.h"
#include "hookfinder.h" #include "hookfinder.h"
#include "util.h" #include "util.h"
#include "MinHook.h"
extern const char* PIPE_CONNECTED; extern const char* PIPE_CONNECTED;
extern const char* INSERTING_HOOK; extern const char* INSERTING_HOOK;

View File

@ -15,6 +15,49 @@ void RemoveHook(uint64_t addr, int maxOffset = 9);
inline SearchParam spDefault; inline SearchParam spDefault;
extern "C" // minhook library
{
enum MH_STATUS
{
MH_OK,
MH_ERROR_ALREADY_INITIALIZED,
MH_ERROR_NOT_INITIALIZED,
MH_ERROR_ALREADY_CREATED,
MH_ERROR_NOT_CREATED,
MH_ERROR_ENABLED,
MH_ERROR_DISABLED,
MH_ERROR_NOT_EXECUTABLE,
MH_ERROR_UNSUPPORTED_FUNCTION,
MH_ERROR_MEMORY_ALLOC,
MH_ERROR_MEMORY_PROTECT,
MH_ERROR_MODULE_NOT_FOUND,
MH_ERROR_FUNCTION_NOT_FOUND
};
MH_STATUS WINAPI MH_Initialize(VOID);
MH_STATUS WINAPI MH_Uninitialize(VOID);
// Creates a Hook for the specified target function, in disabled state.
// Parameters:
// pTarget [in] A pointer to the target function, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
MH_STATUS WINAPI MH_ApplyQueued(VOID);
const char* WINAPI MH_StatusToString(MH_STATUS status);
}
#define MH_ALL_HOOKS NULL
#define ITH_RAISE (*(int*)0 = 0) // raise C000005, for debugging only #define ITH_RAISE (*(int*)0 = 0) // raise C000005, for debugging only
#define ITH_TRY __try #define ITH_TRY __try
#define ITH_EXCEPT __except(EXCEPTION_EXECUTE_HANDLER) #define ITH_EXCEPT __except(EXCEPTION_EXECUTE_HANDLER)

@ -1 +0,0 @@
Subproject commit 423d1e45af2ed2719a5c31e990e935ef301ed9c3

View File

@ -6,7 +6,6 @@
#include "texthook.h" #include "texthook.h"
#include "main.h" #include "main.h"
#include "ithsys/ithsys.h" #include "ithsys/ithsys.h"
#include "MinHook.h"
extern const char* FUNC_MISSING; extern const char* FUNC_MISSING;
extern const char* MODULE_MISSING; extern const char* MODULE_MISSING;
@ -193,7 +192,6 @@ void TextHook::Send(uintptr_t dwDataBase)
TextOutput(tp, buffer, count); TextOutput(tp, buffer, count);
#endif // _WIN64 #endif // _WIN64
++*pbData;
} }
__except (EXCEPTION_EXECUTE_HANDLER) __except (EXCEPTION_EXECUTE_HANDLER)
{ {
@ -309,7 +307,7 @@ int TextHook::GetLength(uintptr_t base, uintptr_t in)
else { else {
if (hp.type & BIG_ENDIAN) if (hp.type & BIG_ENDIAN)
in >>= 8; in >>= 8;
len = !!IsDBCSLeadByteEx(hp.codepage, in & 0xff) + 1; len = LeadByteTable[in & 0xff]; //Slightly faster than IsDBCSLeadByte
} }
break; break;
} }

View File

@ -51,6 +51,6 @@ private:
}; };
enum { MAX_HOOK = 2500, HOOK_BUFFER_SIZE = MAX_HOOK * sizeof(TextHook), HOOK_SECTION_SIZE = HOOK_BUFFER_SIZE * 2 }; enum { MAX_HOOK = 300, HOOK_BUFFER_SIZE = MAX_HOOK * sizeof(TextHook), HOOK_SECTION_SIZE = HOOK_BUFFER_SIZE * 2 };
// EOF // EOF

View File

@ -9,6 +9,32 @@
#include "ithsys/ithsys.h" #include "ithsys/ithsys.h"
#include "const.h" #include "const.h"
// - Global variables -
// jichi 6/12/2015: https://en.wikipedia.org/wiki/Shift_JIS
// Leading table for SHIFT-JIS encoding
BYTE LeadByteTable[0x100] = {
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1
};
// - API functions -
extern "C" {
/** /**
* Return the address of the first matched pattern. * Return the address of the first matched pattern.
* Artikash 7/14/2018: changed implementation, hopefully it behaves the same * Artikash 7/14/2018: changed implementation, hopefully it behaves the same
@ -44,25 +70,15 @@ DWORD IthGetMemoryRange(LPCVOID mem, DWORD *base, DWORD *size)
return info.Protect > PAGE_NOACCESS; return info.Protect > PAGE_NOACCESS;
} }
// jichi 6/12/2015: https://en.wikipedia.org/wiki/Shift_JIS inline DWORD GetHash(LPSTR str)
// Leading table for SHIFT-JIS encoding {
BYTE LeadByteTable[0x100] = { DWORD hash = 0;
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, //for (; *str; str++)
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, while (*str)
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, hash = ((hash >> 7) | (hash << 25)) + *str++;
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, return hash;
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, }
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, } // extern "C"
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,1
};
// EOF // EOF

View File

@ -10,10 +10,14 @@
//#include "ntdll/ntdll.h" //#include "ntdll/ntdll.h"
#include <Windows.h> #include <Windows.h>
// jichi 8/24/2013: Why extern "C"? Any specific reason to use C instead of C++ naming?
extern "C" {
// jichi 10/1/2013: Return 0 if failed. So, it is ambiguous if the search pattern starts at 0 // jichi 10/1/2013: Return 0 if failed. So, it is ambiguous if the search pattern starts at 0
DWORD SearchPattern(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length); // KMP DWORD SearchPattern(DWORD base, DWORD base_length, LPCVOID search, DWORD search_length); // KMP
DWORD IthGetMemoryRange(LPCVOID mem, DWORD *base, DWORD *size); DWORD IthGetMemoryRange(LPCVOID mem, DWORD *base, DWORD *size);
} // extern "C"
extern BYTE LeadByteTable[]; extern BYTE LeadByteTable[];

View File

@ -6,7 +6,6 @@
#include "util/util.h" #include "util/util.h"
#include "ithsys/ithsys.h" #include "ithsys/ithsys.h"
#include "main.h" #include "main.h"
#include <Psapi.h>
namespace { // unnamed namespace { // unnamed
@ -346,15 +345,6 @@ std::vector<uint64_t> SearchMemory(const void* bytes, short length, DWORD protec
return ret; return ret;
} }
uintptr_t FindFunction(const char* function)
{
static HMODULE modules[300] = {};
static auto _ = EnumProcessModules(GetCurrentProcess(), modules, sizeof(modules), DUMMY);
for (auto module : modules) if (auto addr = GetProcAddress(module, function)) return (uintptr_t)addr;
return 0;
}
} }
// EOF // EOF

View File

@ -22,7 +22,6 @@ bool SearchResourceString(LPCWSTR str);
std::pair<uint64_t, uint64_t> QueryModuleLimits(HMODULE module); std::pair<uint64_t, uint64_t> QueryModuleLimits(HMODULE module);
std::vector<uint64_t> SearchMemory(const void* bytes, short length, DWORD protect = PAGE_EXECUTE, uintptr_t minAddr = 0, uintptr_t maxAddr = -1ULL); std::vector<uint64_t> SearchMemory(const void* bytes, short length, DWORD protect = PAGE_EXECUTE, uintptr_t minAddr = 0, uintptr_t maxAddr = -1ULL);
uintptr_t FindFunction(const char* function);
} // namespace Util } // namespace Util

BIN
x64libs/minhook.lib Normal file

Binary file not shown.

BIN
x86libs/minhook.lib Normal file

Binary file not shown.