forked from Public-Mirror/Textractor
Compare commits
1 Commits
master
...
efficient_
Author | SHA1 | Date | |
---|---|---|---|
|
f890789a3b |
@ -1,39 +0,0 @@
|
|||||||
version: '{branch}-{build}'
|
|
||||||
configuration: RelWithDebInfo
|
|
||||||
clone_folder: C:\Textractor
|
|
||||||
skip_branch_with_pr: false
|
|
||||||
|
|
||||||
environment:
|
|
||||||
matrix:
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
|
||||||
arch: x64
|
|
||||||
platform: x64
|
|
||||||
qtbin: msvc2017_64
|
|
||||||
- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
|
||||||
arch: x86
|
|
||||||
platform: Win32
|
|
||||||
qtbin: msvc2017
|
|
||||||
|
|
||||||
before_build:
|
|
||||||
- git submodule update --init
|
|
||||||
- cd C:\
|
|
||||||
- mkdir %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
|
|
||||||
|
|
||||||
build:
|
|
||||||
project: C:\%arch%\Textractor.sln
|
|
||||||
parallel: false
|
|
||||||
verbosity: normal
|
|
||||||
|
|
||||||
after_build:
|
|
||||||
- 7z a Textractor-Alpha%APPVEYOR_BUILD_NUMBER%-%arch%.zip C:\Textractor\builds\RelWithDebInfo_%arch%\*
|
|
||||||
- appveyor PushArtifact Textractor-Alpha%APPVEYOR_BUILD_NUMBER%-%arch%.zip
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
- provider: Email
|
|
||||||
to:
|
|
||||||
- akashmozumdar@gmail.com
|
|
||||||
on_build_success: false
|
|
||||||
on_build_failure: true
|
|
||||||
on_build_status_changed: true
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "texthook/minhook"]
|
|
||||||
path = texthook/minhook
|
|
||||||
url = https://github.com/TsudaKageyu/minhook.git
|
|
@ -1,11 +1,6 @@
|
|||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
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,33 +13,28 @@ 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}>)
|
||||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_FINAL_OUTPUT_DIRECTORY}>)
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_FINAL_OUTPUT_DIRECTORY}>)
|
||||||
|
|
||||||
include_directories(include)
|
include_directories(include)
|
||||||
add_library(pch text.cpp)
|
|
||||||
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) # uncomment to build CLI
|
||||||
|
endif()
|
||||||
|
@ -1,32 +1,29 @@
|
|||||||
{
|
{
|
||||||
|
// 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" ]
|
"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" ]
|
"inheritEnvironments": [ "msvc_x64" ]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
26
CREDITS.md
Normal file
26
CREDITS.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Developers
|
||||||
|
|
||||||
|
If you're on this list and want your link changed let me know.
|
||||||
|
- Textractor mainly made by [Me](https://github.com/Artikash) with the help of
|
||||||
|
- [DoumanAsh](https://github.com/DoumanAsh)
|
||||||
|
- [Niakr1s](https://github.com/Niakr1s)
|
||||||
|
- [tinyAdapter](https://github.com/tinyAdapter)
|
||||||
|
- Spanish translation by [scese250](https://github.com/scese250)
|
||||||
|
- Turkish translation by niisokusu
|
||||||
|
- Simplified Chinese translation by [tinyAdapter](https://github.com/tinyAdapter)
|
||||||
|
- Russian translation by [TokcDK](https://github.com/TokcDK)
|
||||||
|
- Indonesian translation by [Hawxone](https://github.com/Hawxone)
|
||||||
|
- Portuguese translation by [TsumiHokiro](https://github.com/TsumiHokiro)
|
||||||
|
- Thai translation by [azmadoppler](https://github.com/azmadoppler)
|
||||||
|
- Korean translation by O SK
|
||||||
|
- ITHVNR updated by [mireado](https://github.com/mireado), [Eguni](https://github.com/Eguni), and [IJEMIN](https://github.com/IJEMIN)
|
||||||
|
- ITHVNR originally made by [Stomp](http://www.hongfire.com/forum/member/325894-stomp)
|
||||||
|
- VNR engine made by [jichi](https://archive.is/prJwr)
|
||||||
|
- ITH updated by [Andys](https://github.com/AndyScull)
|
||||||
|
- ITH originally made by [kaosu](http://www.hongfire.com/forum/member/562651-kaosu)
|
||||||
|
- Locale Emulator library made by [xupefei](https://github.com/xupefei)
|
||||||
|
- MinHook library made by [TsudaKageyu](https://github.com/TsudaKageyu)
|
||||||
|
|
||||||
|
## Special Thanks
|
||||||
|
|
||||||
|
- Everybody adding issues!
|
@ -1,20 +1,22 @@
|
|||||||
|
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/util.cpp
|
||||||
Textractor.rc
|
Textractor.rc
|
||||||
Textractor.ico
|
Textractor.ico
|
||||||
)
|
)
|
||||||
target_precompile_headers(Textractor REUSE_FROM pch)
|
target_link_libraries(${PROJECT_NAME} Qt5::Widgets shell32 winhttp)
|
||||||
target_link_libraries(Textractor host Qt5::Widgets Qt5::WinExtras 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)
|
||||||
add_custom_command(TARGET Textractor
|
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5Cored.dll)
|
||||||
POST_BUILD
|
install_qt5_libs(${PROJECT_NAME})
|
||||||
COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt"
|
endif()
|
||||||
COMMAND set PATH=%PATH%$<SEMICOLON>${qt5_install_prefix}/bin
|
|
||||||
COMMAND Qt5::windeployqt --dir ${CMAKE_FINAL_OUTPUT_DIRECTORY} "${CMAKE_FINAL_OUTPUT_DIRECTORY}/Textractor.exe"
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
@ -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();
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
@ -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>
|
|
@ -1,5 +1,7 @@
|
|||||||
#include "extenwindow.h"
|
#include "extenwindow.h"
|
||||||
#include "ui_extenwindow.h"
|
#include "ui_extenwindow.h"
|
||||||
|
#include "defs.h"
|
||||||
|
#include <concrt.h>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QDragEnterEvent>
|
#include <QDragEnterEvent>
|
||||||
@ -9,41 +11,34 @@
|
|||||||
|
|
||||||
extern const char* EXTENSIONS;
|
extern const char* EXTENSIONS;
|
||||||
extern const char* ADD_EXTENSION;
|
extern const char* ADD_EXTENSION;
|
||||||
extern const char* REMOVE_EXTENSION;
|
|
||||||
extern const char* INVALID_EXTENSION;
|
extern const char* INVALID_EXTENSION;
|
||||||
extern const char* CONFIRM_EXTENSION_OVERWRITE;
|
extern const char* CONFIRM_EXTENSION_OVERWRITE;
|
||||||
extern const char* EXTENSION_WRITE_ERROR;
|
extern const char* EXTENSION_WRITE_ERROR;
|
||||||
extern const char* EXTEN_WINDOW_INSTRUCTIONS;
|
extern const char* EXTEN_WINDOW_INSTRUCTIONS;
|
||||||
|
|
||||||
|
constexpr auto DEFAULT_EXTENSIONS = u8"Remove Repeated Characters>Remove Repeated Phrases>Regex Filter>Copy to Clipboard>Bing Translate>Extra Window>Extra Newlines";
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
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";
|
|
||||||
|
|
||||||
struct Extension
|
struct Extension
|
||||||
{
|
{
|
||||||
std::wstring name;
|
std::wstring name;
|
||||||
wchar_t* (*callback)(wchar_t*, const InfoForExtension*);
|
wchar_t* (*callback)(wchar_t*, const InfoForExtension*);
|
||||||
};
|
};
|
||||||
|
|
||||||
Ui::ExtenWindow ui;
|
|
||||||
concurrency::reader_writer_lock extenMutex;
|
concurrency::reader_writer_lock extenMutex;
|
||||||
std::vector<Extension> extensions;
|
std::vector<Extension> extensions;
|
||||||
ExtenWindow* This = nullptr;
|
|
||||||
|
|
||||||
bool Load(QString extenName)
|
bool Load(QString extenName)
|
||||||
{
|
{
|
||||||
if (extenName.endsWith(".dll")) extenName.chop(4);
|
// Extension is dll and exports "OnNewSentence"
|
||||||
if (extenName.endsWith(".xdll")) extenName.chop(5);
|
if (QTextFile(extenName + ".dll", QIODevice::ReadOnly).readAll().contains("OnNewSentence"))
|
||||||
if (!QFile::exists(extenName + ".xdll")) QFile::copy(extenName + ".dll", extenName + ".xdll");
|
|
||||||
// Extension must export "OnNewSentence"
|
|
||||||
if (QTextFile(extenName + ".xdll", QIODevice::ReadOnly).readAll().contains("OnNewSentence"))
|
|
||||||
{
|
{
|
||||||
if (HMODULE module = LoadLibraryW(S(extenName + ".xdll").c_str()))
|
if (HMODULE module = LoadLibraryW(S(extenName + ".dll").c_str()))
|
||||||
{
|
{
|
||||||
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,62 +50,19 @@ 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".dll").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); }));
|
||||||
::extensions = extensions;
|
::extensions = extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sync()
|
|
||||||
{
|
|
||||||
ui.extenList->clear();
|
|
||||||
QTextFile extenSaveFile(EXTEN_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate);
|
|
||||||
concurrency::reader_writer_lock::scoped_lock_read readLock(extenMutex);
|
|
||||||
for (auto extension : extensions)
|
|
||||||
{
|
|
||||||
ui.extenList->addItem(S(extension.name));
|
|
||||||
extenSaveFile.write((S(extension.name) + ">").toUtf8());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Add(QFileInfo extenFile)
|
|
||||||
{
|
|
||||||
if (extenFile.suffix() == "dll" || extenFile.suffix() == "xdll")
|
|
||||||
{
|
|
||||||
if (extenFile.absolutePath() != QDir::currentPath())
|
|
||||||
{
|
|
||||||
if (QFile::exists(extenFile.fileName()) && QMessageBox::question(This, EXTENSIONS, CONFIRM_EXTENSION_OVERWRITE) == QMessageBox::Yes) QFile::remove(extenFile.fileName());
|
|
||||||
if (!QFile::copy(extenFile.absoluteFilePath(), extenFile.fileName())) QMessageBox::warning(This, EXTENSIONS, EXTENSION_WRITE_ERROR);
|
|
||||||
}
|
|
||||||
if (Load(extenFile.fileName())) return Sync();
|
|
||||||
}
|
|
||||||
QMessageBox::information(This, EXTENSIONS, QString(INVALID_EXTENSION).arg(extenFile.fileName()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Delete()
|
|
||||||
{
|
|
||||||
if (ui.extenList->currentItem())
|
|
||||||
{
|
|
||||||
Unload(ui.extenList->currentIndex().row());
|
|
||||||
Sync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContextMenu(QPoint point)
|
|
||||||
{
|
|
||||||
QAction addExtension(ADD_EXTENSION), removeExtension(REMOVE_EXTENSION);
|
|
||||||
if (auto action = QMenu::exec({ &addExtension, &removeExtension }, ui.extenList->mapToGlobal(point), nullptr, This))
|
|
||||||
if (action == &removeExtension) Delete();
|
|
||||||
else if (QString extenFile = QFileDialog::getOpenFileName(This, ADD_EXTENSION, ".", EXTENSIONS + QString(" (*.xdll);;Libraries (*.dll)")); !extenFile.isEmpty()) Add(extenFile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DispatchSentenceToExtensions(std::wstring& sentence, const InfoForExtension* sentenceInfo)
|
bool DispatchSentenceToExtensions(std::wstring& sentence, const InfoForExtension* sentenceInfo)
|
||||||
@ -119,7 +71,7 @@ bool DispatchSentenceToExtensions(std::wstring& sentence, const InfoForExtension
|
|||||||
wcscpy_s(sentenceBuffer, sentence.size() + 1, sentence.c_str());
|
wcscpy_s(sentenceBuffer, sentence.size() + 1, sentence.c_str());
|
||||||
concurrency::reader_writer_lock::scoped_lock_read readLock(extenMutex);
|
concurrency::reader_writer_lock::scoped_lock_read readLock(extenMutex);
|
||||||
for (const auto& extension : extensions)
|
for (const auto& extension : extensions)
|
||||||
if (!*(sentenceBuffer = extension.callback(sentenceBuffer, sentenceInfo))) break;
|
if (*(sentenceBuffer = extension.callback(sentenceBuffer, sentenceInfo)) == L'\0') break;
|
||||||
sentence = sentenceBuffer;
|
sentence = sentenceBuffer;
|
||||||
HeapFree(GetProcessHeap(), 0, sentenceBuffer);
|
HeapFree(GetProcessHeap(), 0, sentenceBuffer);
|
||||||
return !sentence.empty();
|
return !sentence.empty();
|
||||||
@ -127,33 +79,71 @@ 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".dll").c_str()));
|
||||||
extensions.clear();
|
extensions.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
ExtenWindow::ExtenWindow(QWidget* parent) : QMainWindow(parent, Qt::WindowCloseButtonHint)
|
ExtenWindow::ExtenWindow(QWidget* parent) :
|
||||||
|
QMainWindow(parent, Qt::WindowCloseButtonHint),
|
||||||
|
ui(new Ui::ExtenWindow)
|
||||||
{
|
{
|
||||||
This = this;
|
ui->setupUi(this);
|
||||||
ui.setupUi(this);
|
|
||||||
ui.vboxLayout->addWidget(new QLabel(EXTEN_WINDOW_INSTRUCTIONS, this));
|
connect(ui->extenList, &QListWidget::customContextMenuRequested, [this](QPoint point)
|
||||||
|
{
|
||||||
|
if (QMenu(this).exec({ std::make_unique<QAction>(ADD_EXTENSION).get() }, ui->extenList->mapToGlobal(point)))
|
||||||
|
if (QString extenFile = QFileDialog::getOpenFileName(this, ADD_EXTENSION, ".", EXTENSIONS + QString(" (*.dll)")); !extenFile.isEmpty()) Add(extenFile);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui->vboxLayout->addWidget(new QLabel(EXTEN_WINDOW_INSTRUCTIONS, this));
|
||||||
setWindowTitle(EXTENSIONS);
|
setWindowTitle(EXTENSIONS);
|
||||||
|
|
||||||
connect(ui.extenList, &QListWidget::customContextMenuRequested, ContextMenu);
|
ui->extenList->installEventFilter(this);
|
||||||
ui.extenList->installEventFilter(this);
|
|
||||||
|
|
||||||
if (!QFile::exists(EXTEN_SAVE_FILE)) QTextFile(EXTEN_SAVE_FILE, QIODevice::WriteOnly).write(DEFAULT_EXTENSIONS);
|
if (!QFile::exists(EXTEN_SAVE_FILE)) QTextFile(EXTEN_SAVE_FILE, QIODevice::WriteOnly).write(DEFAULT_EXTENSIONS);
|
||||||
for (auto extenName : QString(QTextFile(EXTEN_SAVE_FILE, QIODevice::ReadOnly).readAll()).split(">")) Load(extenName);
|
for (auto extenName : QString(QTextFile(EXTEN_SAVE_FILE, QIODevice::ReadOnly).readAll()).split(">")) Load(extenName);
|
||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExtenWindow::~ExtenWindow()
|
||||||
|
{
|
||||||
|
delete ui;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtenWindow::Add(QFileInfo extenFile)
|
||||||
|
{
|
||||||
|
if (extenFile.suffix() == "dll")
|
||||||
|
{
|
||||||
|
if (extenFile.absolutePath() != QDir::currentPath())
|
||||||
|
{
|
||||||
|
if (QFile::exists(extenFile.fileName()) && QMessageBox::question(this, EXTENSIONS, CONFIRM_EXTENSION_OVERWRITE) == QMessageBox::Yes) QFile::remove(extenFile.fileName());
|
||||||
|
if (!QFile::copy(extenFile.absoluteFilePath(), extenFile.fileName())) QMessageBox::warning(this, EXTENSIONS, EXTENSION_WRITE_ERROR);
|
||||||
|
}
|
||||||
|
if (Load(extenFile.completeBaseName())) return Sync();
|
||||||
|
}
|
||||||
|
QMessageBox::information(this, EXTENSIONS, QString(INVALID_EXTENSION).arg(extenFile.fileName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtenWindow::Sync()
|
||||||
|
{
|
||||||
|
ui->extenList->clear();
|
||||||
|
QTextFile extenSaveFile(EXTEN_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate);
|
||||||
|
concurrency::reader_writer_lock::scoped_lock_read readLock(extenMutex);
|
||||||
|
for (auto extension : extensions)
|
||||||
|
{
|
||||||
|
ui->extenList->addItem(S(extension.name));
|
||||||
|
extenSaveFile.write((S(extension.name) + ">").toUtf8());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool ExtenWindow::eventFilter(QObject* target, QEvent* event)
|
bool ExtenWindow::eventFilter(QObject* target, QEvent* event)
|
||||||
{
|
{
|
||||||
// https://stackoverflow.com/questions/1224432/how-do-i-respond-to-an-internal-drag-and-drop-operation-using-a-qlistwidget/1528215
|
// See https://stackoverflow.com/questions/1224432/how-do-i-respond-to-an-internal-drag-and-drop-operation-using-a-qlistwidget/1528215
|
||||||
if (event->type() == QEvent::ChildRemoved)
|
if (event->type() == QEvent::ChildRemoved)
|
||||||
{
|
{
|
||||||
QStringList extenNames;
|
QStringList extenNames;
|
||||||
for (int i = 0; i < ui.extenList->count(); ++i) extenNames.push_back(ui.extenList->item(i)->text());
|
for (int i = 0; i < ui->extenList->count(); ++i) extenNames.push_back(ui->extenList->item(i)->text());
|
||||||
Reorder(extenNames);
|
Reorder(extenNames);
|
||||||
Sync();
|
Sync();
|
||||||
}
|
}
|
||||||
@ -162,7 +152,11 @@ bool ExtenWindow::eventFilter(QObject* target, QEvent* event)
|
|||||||
|
|
||||||
void ExtenWindow::keyPressEvent(QKeyEvent* event)
|
void ExtenWindow::keyPressEvent(QKeyEvent* event)
|
||||||
{
|
{
|
||||||
if (event->key() == Qt::Key_Delete) Delete();
|
if (event->key() == Qt::Key_Delete && ui->extenList->currentItem())
|
||||||
|
{
|
||||||
|
Unload(ui->extenList->currentIndex().row());
|
||||||
|
Sync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExtenWindow::dragEnterEvent(QDragEnterEvent* event)
|
void ExtenWindow::dragEnterEvent(QDragEnterEvent* event)
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class ExtenWindow;
|
||||||
|
}
|
||||||
|
|
||||||
struct InfoForExtension
|
struct InfoForExtension
|
||||||
{
|
{
|
||||||
const char* name;
|
const char* name;
|
||||||
@ -15,10 +20,17 @@ class ExtenWindow : public QMainWindow
|
|||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ExtenWindow(QWidget* parent = nullptr);
|
explicit ExtenWindow(QWidget* parent = nullptr);
|
||||||
|
~ExtenWindow();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
inline static constexpr auto EXTEN_SAVE_FILE = u8"SavedExtensions.txt";
|
||||||
|
|
||||||
|
void Add(QFileInfo extenFile);
|
||||||
|
void Sync();
|
||||||
bool eventFilter(QObject* target, QEvent* event) override;
|
bool eventFilter(QObject* target, QEvent* event) override;
|
||||||
void keyPressEvent(QKeyEvent* event) override;
|
void keyPressEvent(QKeyEvent* event) override;
|
||||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||||
void dropEvent(QDropEvent* event) override;
|
void dropEvent(QDropEvent* event) override;
|
||||||
|
|
||||||
|
Ui::ExtenWindow* ui;
|
||||||
};
|
};
|
||||||
|
8
GUI/host/CMakeLists.txt
Normal file
8
GUI/host/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 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
|
||||||
|
cli.cpp
|
||||||
|
exception.cpp
|
||||||
|
host.cpp
|
||||||
|
textthread.cpp
|
||||||
|
util.cpp
|
||||||
|
)
|
@ -1,5 +1,5 @@
|
|||||||
#include "../host.h"
|
#include "host.h"
|
||||||
#include "../hookcode.h"
|
#include "util.h"
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
@ -12,16 +12,7 @@ int main()
|
|||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
Host::Start([](auto) {}, [](auto) {}, [](auto&) {}, [](auto&) {}, [](TextThread& thread, std::wstring& output)
|
Host::Start([](auto) {}, [](auto) {}, [](auto&) {}, [](auto&) {}, [](TextThread& thread, std::wstring& output)
|
||||||
{
|
{
|
||||||
wprintf_s(L"[%I64X:%I32X:%I64X:%I64X:%I64X:%s:%s] %s\n",
|
wprintf_s(L"[%I64X:%I32X:%I64X:%I64X:%I64X:%s:%s] %s\n", thread.handle, thread.tp.processId, thread.tp.addr, thread.tp.ctx, thread.tp.ctx2, thread.name.c_str(), Util::GenerateCode(thread.hp, thread.tp.processId).c_str(), output.c_str());
|
||||||
thread.handle,
|
|
||||||
thread.tp.processId,
|
|
||||||
thread.tp.addr,
|
|
||||||
thread.tp.ctx,
|
|
||||||
thread.tp.ctx2,
|
|
||||||
thread.name.c_str(),
|
|
||||||
HookCode::Generate(thread.hp, thread.tp.processId).c_str(),
|
|
||||||
output.c_str()
|
|
||||||
);
|
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
@ -33,7 +24,7 @@ int main()
|
|||||||
if (swscanf(input, L"%500s -P%d", command, &processId) != 2) ExitProcess(0);
|
if (swscanf(input, L"%500s -P%d", command, &processId) != 2) ExitProcess(0);
|
||||||
if (_wcsicmp(command, L"attach") == 0) Host::InjectProcess(processId);
|
if (_wcsicmp(command, L"attach") == 0) Host::InjectProcess(processId);
|
||||||
else if (_wcsicmp(command, L"detach") == 0) Host::DetachProcess(processId);
|
else if (_wcsicmp(command, L"detach") == 0) Host::DetachProcess(processId);
|
||||||
else if (auto hp = HookCode::Parse(command)) Host::InsertHook(processId, hp.value());
|
else if (auto hp = Util::ParseCode(command)) Host::InsertHook(processId, hp.value());
|
||||||
else ExitProcess(0);
|
else ExitProcess(0);
|
||||||
}
|
}
|
||||||
ExitProcess(0);
|
ExitProcess(0);
|
@ -1,11 +1,11 @@
|
|||||||
#include "module.h"
|
#include "util.h"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
char* GetCppExceptionInfo(EXCEPTION_POINTERS* exception)
|
char* GetCppExceptionInfo(EXCEPTION_POINTERS* exception)
|
||||||
{
|
{
|
||||||
// https://blogs.msdn.microsoft.com/oldnewthing/20100730-00/?p=13273
|
// See https://blogs.msdn.microsoft.com/oldnewthing/20100730-00/?p=13273
|
||||||
// Not very reliable so use __try
|
// Not very reliable so use __try
|
||||||
__try { return ((char****)exception->ExceptionRecord->ExceptionInformation[2])[3][1][1] + 8; }
|
__try { return ((char****)exception->ExceptionRecord->ExceptionInformation[2])[3][1][1] + 8; }
|
||||||
__except (EXCEPTION_EXECUTE_HANDLER) { return "Could not find"; }
|
__except (EXCEPTION_EXECUTE_HANDLER) { return "Could not find"; }
|
||||||
@ -21,11 +21,8 @@ namespace
|
|||||||
|
|
||||||
__declspec(noreturn) void Terminate()
|
__declspec(noreturn) void Terminate()
|
||||||
{
|
{
|
||||||
WaitForSingleObject(CreateThread(nullptr, 0, [](void* lastError) -> DWORD
|
MessageBoxW(NULL, lastError.c_str(), L"Textractor ERROR", MB_ICONERROR);
|
||||||
{
|
abort();
|
||||||
MessageBoxW(NULL, (wchar_t*)lastError, L"Textractor ERROR", MB_ICONERROR); // might fail to display if called in main thread and exception was in main event loop
|
|
||||||
abort();
|
|
||||||
}, lastError.data(), 0, nullptr), INFINITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LONG WINAPI ExceptionLogger(EXCEPTION_POINTERS* exception)
|
LONG WINAPI ExceptionLogger(EXCEPTION_POINTERS* exception)
|
||||||
@ -39,7 +36,7 @@ namespace
|
|||||||
errorMsg << std::uppercase << std::hex <<
|
errorMsg << std::uppercase << std::hex <<
|
||||||
L"Error code: " << exception->ExceptionRecord->ExceptionCode << std::endl <<
|
L"Error code: " << exception->ExceptionRecord->ExceptionCode << std::endl <<
|
||||||
L"Error address: " << exception->ExceptionRecord->ExceptionAddress << std::endl <<
|
L"Error address: " << exception->ExceptionRecord->ExceptionAddress << std::endl <<
|
||||||
L"Error in module: " << GetModuleFilename((HMODULE)info.AllocationBase).value_or(L"Could not find") << std::endl <<
|
L"Error in module: " << Util::GetModuleFilename((HMODULE)info.AllocationBase).value_or(L"Could not find") << std::endl <<
|
||||||
L"Additional info: " << info.AllocationBase << std::endl;
|
L"Additional info: " << info.AllocationBase << std::endl;
|
||||||
|
|
||||||
if (exception->ExceptionRecord->ExceptionCode == 0xE06D7363)
|
if (exception->ExceptionRecord->ExceptionCode == 0xE06D7363)
|
@ -1,8 +1,8 @@
|
|||||||
#include "host.h"
|
#include "host.h"
|
||||||
#include "defs.h"
|
#include "defs.h"
|
||||||
#include "module.h"
|
#include "util.h"
|
||||||
#include "hookcode.h"
|
|
||||||
#include "../texthook/texthook.h"
|
#include "../texthook/texthook.h"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
extern const wchar_t* ALREADY_INJECTED;
|
extern const wchar_t* ALREADY_INJECTED;
|
||||||
extern const wchar_t* NEED_32_BIT;
|
extern const wchar_t* NEED_32_BIT;
|
||||||
@ -32,7 +32,8 @@ namespace
|
|||||||
{
|
{
|
||||||
if (!view) return {};
|
if (!view) return {};
|
||||||
std::scoped_lock lock(viewMutex);
|
std::scoped_lock lock(viewMutex);
|
||||||
for (auto hook : view) if (hook.address == addr) return hook;
|
for (auto hook : view)
|
||||||
|
if (hook.address == addr) return hook;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +49,7 @@ namespace
|
|||||||
|
|
||||||
Host::HookEventHandler OnHookFound = [](HookParam hp, std::wstring text)
|
Host::HookEventHandler OnHookFound = [](HookParam hp, std::wstring text)
|
||||||
{
|
{
|
||||||
Host::AddConsoleOutput(HookCode::Generate(hp) + L": " + text);
|
Host::AddConsoleOutput(Util::GenerateCode(hp) + L": " + text);
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -68,7 +69,10 @@ namespace
|
|||||||
void RemoveThreads(std::function<bool(ThreadParam)> removeIf)
|
void RemoveThreads(std::function<bool(ThreadParam)> removeIf)
|
||||||
{
|
{
|
||||||
std::vector<TextThread*> threadsToRemove;
|
std::vector<TextThread*> threadsToRemove;
|
||||||
for (auto& [tp, thread] : textThreadsByParams.Acquire().contents) if (removeIf(tp)) threadsToRemove.push_back(&thread);
|
{
|
||||||
|
auto textThreadsByParams = ::textThreadsByParams.Acquire();
|
||||||
|
std::for_each(textThreadsByParams->begin(), textThreadsByParams->end(), [&](auto& it) { if (removeIf(it.first)) threadsToRemove.push_back(&it.second); });
|
||||||
|
}
|
||||||
for (auto thread : threadsToRemove)
|
for (auto thread : threadsToRemove)
|
||||||
{
|
{
|
||||||
OnDestroy(*thread);
|
OnDestroy(*thread);
|
||||||
@ -106,9 +110,9 @@ namespace
|
|||||||
std::wstring wide = info.text;
|
std::wstring wide = info.text;
|
||||||
if (wide.size() > STRING) OnHookFound(info.hp, std::move(info.text));
|
if (wide.size() > STRING) OnHookFound(info.hp, std::move(info.text));
|
||||||
info.hp.type &= ~USING_UNICODE;
|
info.hp.type &= ~USING_UNICODE;
|
||||||
if (auto converted = StringToWideString((char*)info.text, info.hp.codepage))
|
if (auto converted = Util::StringToWideString((char*)info.text, info.hp.codepage))
|
||||||
if (converted->size() > STRING) OnHookFound(info.hp, std::move(converted.value()));
|
if (converted->size() > STRING) OnHookFound(info.hp, std::move(converted.value()));
|
||||||
if (auto converted = StringToWideString((char*)info.text, info.hp.codepage = CP_UTF8))
|
if (auto converted = Util::StringToWideString((char*)info.text, info.hp.codepage = CP_UTF8))
|
||||||
if (converted->size() > STRING) OnHookFound(info.hp, std::move(converted.value()));
|
if (converted->size() > STRING) OnHookFound(info.hp, std::move(converted.value()));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -121,21 +125,21 @@ namespace
|
|||||||
case HOST_NOTIFICATION_TEXT:
|
case HOST_NOTIFICATION_TEXT:
|
||||||
{
|
{
|
||||||
auto info = *(ConsoleOutputNotif*)buffer;
|
auto info = *(ConsoleOutputNotif*)buffer;
|
||||||
Host::AddConsoleOutput(StringToWideString(info.message));
|
Host::AddConsoleOutput(Util::StringToWideString(info.message).value());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
auto tp = *(ThreadParam*)buffer;
|
auto tp = *(ThreadParam*)buffer;
|
||||||
auto textThreadsByParams = ::textThreadsByParams.Acquire();
|
auto textThreadsByParams = ::textThreadsByParams.Acquire();
|
||||||
auto thread = textThreadsByParams->find(tp);
|
auto textThread = textThreadsByParams->find(tp);
|
||||||
if (thread == textThreadsByParams->end())
|
if (textThread == textThreadsByParams->end())
|
||||||
{
|
{
|
||||||
try { thread = textThreadsByParams->try_emplace(tp, tp, processRecordsByIds->at(tp.processId).GetHook(tp.addr).hp).first; }
|
try { textThread = textThreadsByParams->try_emplace(tp, tp, processRecordsByIds->at(tp.processId).GetHook(tp.addr).hp).first; }
|
||||||
catch (std::out_of_range) { continue; } // probably garbage data in pipe, try again
|
catch (std::out_of_range) { continue; } // probably garbage data in pipe, try again
|
||||||
OnCreate(thread->second);
|
OnCreate(textThread->second);
|
||||||
}
|
}
|
||||||
thread->second.Push(buffer + sizeof(tp), bytesRead - sizeof(tp));
|
textThread->second.Push(buffer + sizeof(tp), bytesRead - sizeof(tp));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -178,12 +182,8 @@ namespace Host
|
|||||||
for (int retry = 0; !clipboardText && retry < 3; ++retry) // retry loop in case something else is using the clipboard
|
for (int retry = 0; !clipboardText && retry < 3; ++retry) // retry loop in case something else is using the clipboard
|
||||||
{
|
{
|
||||||
Sleep(10);
|
Sleep(10);
|
||||||
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) continue;
|
if (clipboardText = Util::GetClipboardText()) GetThread(clipboard).AddSentence(std::move(clipboardText.value()));
|
||||||
if (!OpenClipboard(NULL)) continue;
|
|
||||||
if (AutoHandle<Functor<GlobalUnlock>> clipboard = GetClipboardData(CF_UNICODETEXT)) clipboardText = (wchar_t*)GlobalLock(clipboard);
|
|
||||||
CloseClipboard();
|
|
||||||
}
|
}
|
||||||
if (clipboardText) GetThread(clipboard).AddSentence(std::move(clipboardText.value()));
|
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
}).detach();
|
}).detach();
|
||||||
@ -205,7 +205,7 @@ namespace Host
|
|||||||
IsWow64Process(process, &invalidProcess);
|
IsWow64Process(process, &invalidProcess);
|
||||||
if (invalidProcess) return AddConsoleOutput(NEED_32_BIT);
|
if (invalidProcess) return AddConsoleOutput(NEED_32_BIT);
|
||||||
#endif
|
#endif
|
||||||
static std::wstring location = std::filesystem::path(GetModuleFilename().value()).replace_filename(ITH_DLL);
|
static std::wstring location = std::filesystem::current_path().wstring() + L"\\" + ITH_DLL;
|
||||||
if (LPVOID remoteData = VirtualAllocEx(process, nullptr, (location.size() + 1) * sizeof(wchar_t), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))
|
if (LPVOID remoteData = VirtualAllocEx(process, nullptr, (location.size() + 1) * sizeof(wchar_t), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE))
|
||||||
{
|
{
|
||||||
WriteProcessMemory(process, remoteData, location.c_str(), (location.size() + 1) * sizeof(wchar_t), nullptr);
|
WriteProcessMemory(process, remoteData, location.c_str(), (location.size() + 1) * sizeof(wchar_t), nullptr);
|
||||||
@ -248,8 +248,9 @@ namespace Host
|
|||||||
|
|
||||||
TextThread* GetThread(int64_t handle)
|
TextThread* GetThread(int64_t handle)
|
||||||
{
|
{
|
||||||
for (auto& [tp, thread] : textThreadsByParams.Acquire().contents) if (thread.handle == handle) return &thread;
|
auto textThreadsByParams = ::textThreadsByParams.Acquire();
|
||||||
return nullptr;
|
auto thread = std::find_if(textThreadsByParams->begin(), textThreadsByParams->end(), [&](const auto& thread) { return thread.second.handle == handle; });
|
||||||
|
return thread != textThreadsByParams->end() ? &thread->second : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddConsoleOutput(std::wstring text)
|
void AddConsoleOutput(std::wstring text)
|
@ -1,10 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include "textthread.h"
|
#include "textthread.h"
|
||||||
|
|
||||||
namespace Host
|
namespace Host
|
||||||
{
|
{
|
||||||
using ProcessEventHandler = void(*)(DWORD);
|
using ProcessEventHandler = std::function<void(DWORD)>;
|
||||||
using ThreadEventHandler = std::function<void(TextThread&)>;
|
using ThreadEventHandler = std::function<void(TextThread&)>;
|
||||||
using HookEventHandler = std::function<void(HookParam, std::wstring text)>;
|
using HookEventHandler = std::function<void(HookParam, std::wstring text)>;
|
||||||
void Start(ProcessEventHandler Connect, ProcessEventHandler Disconnect, ThreadEventHandler Create, ThreadEventHandler Destroy, TextThread::OutputCallback Output);
|
void Start(ProcessEventHandler Connect, ProcessEventHandler Disconnect, ThreadEventHandler Create, ThreadEventHandler Destroy, TextThread::OutputCallback Output);
|
@ -1,5 +1,6 @@
|
|||||||
#include "textthread.h"
|
#include "textthread.h"
|
||||||
#include "host.h"
|
#include "host.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
extern const wchar_t* INVALID_CODEPAGE;
|
extern const wchar_t* INVALID_CODEPAGE;
|
||||||
|
|
||||||
@ -15,14 +16,14 @@ static bool RemoveRepetition(std::wstring& text)
|
|||||||
|
|
||||||
TextThread::TextThread(ThreadParam tp, HookParam hp, std::optional<std::wstring> name) :
|
TextThread::TextThread(ThreadParam tp, HookParam hp, std::optional<std::wstring> name) :
|
||||||
handle(threadCounter++),
|
handle(threadCounter++),
|
||||||
name(name.value_or(StringToWideString(hp.name))),
|
name(name.value_or(Util::StringToWideString(hp.name).value())),
|
||||||
tp(tp),
|
tp(tp),
|
||||||
hp(hp)
|
hp(hp)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
void TextThread::Start()
|
void TextThread::Start()
|
||||||
{
|
{
|
||||||
CreateTimerQueueTimer(&timer, NULL, [](void* This, auto) { ((TextThread*)This)->Flush(); }, this, 10, 10, WT_EXECUTELONGFUNCTION);
|
CreateTimerQueueTimer(&timer, NULL, [](void* This, BOOLEAN) { ((TextThread*)This)->Flush(); }, this, 10, 10, WT_EXECUTELONGFUNCTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextThread::Stop()
|
void TextThread::Stop()
|
||||||
@ -42,34 +43,22 @@ void TextThread::Push(BYTE* data, int length)
|
|||||||
|
|
||||||
BYTE doubleByteChar[2];
|
BYTE doubleByteChar[2];
|
||||||
if (length == 1) // doublebyte characters must be processed as pairs
|
if (length == 1) // doublebyte characters must be processed as pairs
|
||||||
{
|
if (leadByte) std::tie(doubleByteChar[0], doubleByteChar[1], data, length, leadByte) = std::tuple(leadByte, data[0], doubleByteChar, 2, 0);
|
||||||
if (leadByte)
|
else if (IsDBCSLeadByteEx(hp.codepage ? hp.codepage : Host::defaultCodepage, data[0])) std::tie(leadByte, length) = std::tuple(data[0], 0);
|
||||||
{
|
|
||||||
doubleByteChar[0] = leadByte;
|
|
||||||
doubleByteChar[1] = data[0];
|
|
||||||
data = doubleByteChar;
|
|
||||||
length = 2;
|
|
||||||
leadByte = 0;
|
|
||||||
}
|
|
||||||
else if (IsDBCSLeadByteEx(hp.codepage ? hp.codepage : Host::defaultCodepage, data[0]))
|
|
||||||
{
|
|
||||||
leadByte = data[0];
|
|
||||||
length = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hp.type & HEX_DUMP) for (int i = 0; i < length; i += sizeof(short)) buffer.append(FormatString(L"%04hX ", *(short*)(data + i)));
|
if (hp.type & HEX_DUMP) for (int i = 0; i < length; i += sizeof(short)) buffer.append(FormatString(L"%04hX ", *(short*)(data + i)));
|
||||||
else if (hp.type & USING_UNICODE) buffer.append((wchar_t*)data, length / sizeof(wchar_t));
|
else if (hp.type & USING_UNICODE) buffer.append((wchar_t*)data, length / sizeof(wchar_t));
|
||||||
else if (auto converted = StringToWideString(std::string((char*)data, length), hp.codepage ? hp.codepage : Host::defaultCodepage)) buffer.append(converted.value());
|
else if (auto converted = Util::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)
|
||||||
{
|
{
|
||||||
if (std::all_of(buffer.begin(), buffer.end(), [&](wchar_t ch) { return repeatingChars.find(ch) != repeatingChars.end(); })) buffer.clear();
|
if (std::all_of(buffer.begin(), buffer.end(), [&](wchar_t ch) { return repeatingChars.find(ch) != repeatingChars.end(); })) buffer.clear();
|
||||||
if (RemoveRepetition(buffer)) // sentence repetition detected, which means the entire sentence has already been received
|
if (RemoveRepetition(buffer)) // sentence repetition detected, which means the entire sentence has already been received
|
||||||
{
|
{
|
||||||
|
if (hp.type & BLOCK_FLOOD) Host::RemoveHook(tp.processId, tp.addr);
|
||||||
repeatingChars = std::unordered_set(buffer.begin(), buffer.end());
|
repeatingChars = std::unordered_set(buffer.begin(), buffer.end());
|
||||||
AddSentence(std::move(buffer));
|
AddSentence(std::move(buffer));
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
@ -83,14 +72,6 @@ void TextThread::Push(BYTE* data, int length)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextThread::Push(const wchar_t* data)
|
|
||||||
{
|
|
||||||
std::scoped_lock lock(bufferMutex);
|
|
||||||
// not sure if this should filter repetition
|
|
||||||
lastPushTime = GetTickCount64();
|
|
||||||
buffer += data;
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextThread::Flush()
|
void TextThread::Flush()
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@ -98,19 +79,20 @@ void TextThread::Flush()
|
|||||||
if (storage->size() > maxHistorySize) storage->erase(0, storage->size() - maxHistorySize); // https://github.com/Artikash/Textractor/issues/127#issuecomment-486882983
|
if (storage->size() > maxHistorySize) storage->erase(0, storage->size() - maxHistorySize); // https://github.com/Artikash/Textractor/issues/127#issuecomment-486882983
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::wstring> sentences;
|
std::deque<std::wstring> sentences;
|
||||||
queuedSentences->swap(sentences);
|
queuedSentences->swap(sentences);
|
||||||
int totalSize = 0;
|
int totalSize = 0;
|
||||||
for (auto& sentence : sentences)
|
for (auto& sentence : sentences)
|
||||||
{
|
{
|
||||||
totalSize += sentence.size();
|
totalSize += sentence.size();
|
||||||
sentence.erase(std::remove(sentence.begin(), sentence.end(), 0), sentence.end());
|
sentence.erase(std::remove(sentence.begin(), sentence.end(), L'\0'));
|
||||||
if (Output(*this, sentence)) storage->append(sentence);
|
if (Output(*this, sentence)) storage->append(sentence);
|
||||||
}
|
}
|
||||||
|
if (hp.type & BLOCK_FLOOD && totalSize > PIPE_BUFFER_SIZE) Host::RemoveHook(tp.processId, tp.addr);
|
||||||
|
|
||||||
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();
|
@ -1,16 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
class TextThread
|
class TextThread
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using OutputCallback = bool(*)(TextThread&, std::wstring&);
|
using OutputCallback = std::function<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 = {});
|
||||||
@ -19,7 +20,6 @@ public:
|
|||||||
void Stop();
|
void Stop();
|
||||||
void AddSentence(std::wstring sentence);
|
void AddSentence(std::wstring sentence);
|
||||||
void Push(BYTE* data, int length);
|
void Push(BYTE* data, int length);
|
||||||
void Push(const wchar_t* data);
|
|
||||||
|
|
||||||
Synchronized<std::wstring> storage;
|
Synchronized<std::wstring> storage;
|
||||||
const int64_t handle;
|
const int64_t handle;
|
||||||
@ -36,8 +36,8 @@ 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::deque<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;
|
||||||
};
|
};
|
@ -1,5 +1,5 @@
|
|||||||
#include "hookcode.h"
|
#include "util.h"
|
||||||
#include "module.h"
|
#include <Psapi.h>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@ -88,7 +88,7 @@ namespace
|
|||||||
}
|
}
|
||||||
HCode.erase(0, 1);
|
HCode.erase(0, 1);
|
||||||
|
|
||||||
if (hp.type & USING_STRING)
|
if ((hp.type & USING_STRING))
|
||||||
{
|
{
|
||||||
if (HCode[0] == L'F')
|
if (HCode[0] == L'F')
|
||||||
{
|
{
|
||||||
@ -125,43 +125,36 @@ namespace
|
|||||||
HCode.erase(0, match[0].length());
|
HCode.erase(0, match[0].length());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto ConsumeHexInt = [&HCode]
|
|
||||||
{
|
|
||||||
size_t size = 0;
|
|
||||||
int value = 0;
|
|
||||||
try { value = std::stoi(HCode, &size, 16); } catch (std::invalid_argument) {}
|
|
||||||
HCode.erase(0, size);
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// data_offset
|
// data_offset
|
||||||
hp.offset = ConsumeHexInt();
|
if (!std::regex_search(HCode, match, std::wregex(L"^-?[[:xdigit:]]+"))) return {};
|
||||||
|
hp.offset = std::stoi(match[0], nullptr, 16);
|
||||||
|
HCode.erase(0, match[0].length());
|
||||||
|
|
||||||
// [*deref_offset1]
|
// [*deref_offset1]
|
||||||
if (HCode[0] == L'*')
|
if (std::regex_search(HCode, match, std::wregex(L"^\\*(-?[[:xdigit:]]+)")))
|
||||||
{
|
{
|
||||||
hp.type |= DATA_INDIRECT;
|
hp.type |= DATA_INDIRECT;
|
||||||
HCode.erase(0, 1);
|
hp.index = std::stoi(match[1], nullptr, 16);
|
||||||
hp.index = ConsumeHexInt();
|
HCode.erase(0, match[0].length());
|
||||||
}
|
}
|
||||||
|
|
||||||
// [:split_offset[*deref_offset2]]
|
// [:split_offset[*deref_offset2]]
|
||||||
if (HCode[0] == L':')
|
if (std::regex_search(HCode, match, std::wregex(L"^:(-?[[:xdigit:]]+)")))
|
||||||
{
|
{
|
||||||
hp.type |= USING_SPLIT;
|
hp.type |= USING_SPLIT;
|
||||||
HCode.erase(0, 1);
|
hp.split = std::stoi(match[1], nullptr, 16);
|
||||||
hp.split = ConsumeHexInt();
|
HCode.erase(0, match[0].length());
|
||||||
|
|
||||||
if (HCode[0] == L'*')
|
if (std::regex_search(HCode, match, std::wregex(L"^\\*(-?[[:xdigit:]]+)")))
|
||||||
{
|
{
|
||||||
hp.type |= SPLIT_INDIRECT;
|
hp.type |= SPLIT_INDIRECT;
|
||||||
HCode.erase(0, 1);
|
hp.split_index = std::stoi(match[1], nullptr, 16);
|
||||||
hp.split_index = ConsumeHexInt();
|
HCode.erase(0, match[0].length());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @addr[:module[:func]]
|
// @addr[:module[:func]]
|
||||||
if (!std::regex_match(HCode, match, std::wregex(L"^@([[:xdigit:]]+)(:.+?)?(:.+)?"))) return {};
|
if (!std::regex_match(HCode, match, std::wregex(L"@([[:xdigit:]]+)(:.+?)?(:.+)?"))) return {};
|
||||||
hp.address = std::stoull(match[1], nullptr, 16);
|
hp.address = std::stoull(match[1], nullptr, 16);
|
||||||
if (match[2].matched)
|
if (match[2].matched)
|
||||||
{
|
{
|
||||||
@ -257,7 +250,7 @@ namespace
|
|||||||
if (processId && !(hp.type & MODULE_OFFSET))
|
if (processId && !(hp.type & MODULE_OFFSET))
|
||||||
if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
|
if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
|
||||||
if (MEMORY_BASIC_INFORMATION info = {}; VirtualQueryEx(process, (LPCVOID)hp.address, &info, sizeof(info)))
|
if (MEMORY_BASIC_INFORMATION info = {}; VirtualQueryEx(process, (LPCVOID)hp.address, &info, sizeof(info)))
|
||||||
if (auto moduleName = GetModuleFilename(processId, (HMODULE)info.AllocationBase))
|
if (auto moduleName = Util::GetModuleFilename(processId, (HMODULE)info.AllocationBase))
|
||||||
{
|
{
|
||||||
hp.type |= MODULE_OFFSET;
|
hp.type |= MODULE_OFFSET;
|
||||||
hp.address -= (uint64_t)info.AllocationBase;
|
hp.address -= (uint64_t)info.AllocationBase;
|
||||||
@ -266,39 +259,80 @@ namespace
|
|||||||
|
|
||||||
HCode += L'@' + HexString(hp.address);
|
HCode += L'@' + HexString(hp.address);
|
||||||
if (hp.type & MODULE_OFFSET) HCode += L':' + std::wstring(hp.module);
|
if (hp.type & MODULE_OFFSET) HCode += L':' + std::wstring(hp.module);
|
||||||
if (hp.type & FUNCTION_OFFSET) HCode += L':' + StringToWideString(hp.function);
|
if (hp.type & FUNCTION_OFFSET) HCode += L':' + std::wstring(hp.function, hp.function + MAX_MODULE_SIZE);
|
||||||
|
|
||||||
return HCode;
|
return HCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace HookCode
|
namespace Util
|
||||||
{
|
{
|
||||||
std::optional<HookParam> Parse(std::wstring code)
|
std::optional<std::wstring> GetModuleFilename(DWORD processId, HMODULE module)
|
||||||
{
|
{
|
||||||
if (code[0] == L'/') code.erase(0, 1);
|
std::vector<wchar_t> buffer(MAX_PATH);
|
||||||
code.erase(std::find(code.begin(), code.end(), L'/'), code.end()); // legacy/AGTH compatibility
|
if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
|
||||||
Trim(code);
|
if (GetModuleFileNameExW(process, module, buffer.data(), MAX_PATH)) return buffer.data();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::wstring> GetModuleFilename(HMODULE module)
|
||||||
|
{
|
||||||
|
std::vector<wchar_t> buffer(MAX_PATH);
|
||||||
|
if (GetModuleFileNameW(module, buffer.data(), MAX_PATH)) return buffer.data();
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<DWORD, std::optional<std::wstring>>> GetAllProcesses()
|
||||||
|
{
|
||||||
|
std::vector<DWORD> processIds(10000);
|
||||||
|
DWORD spaceUsed = 0;
|
||||||
|
EnumProcesses(processIds.data(), 10000 * sizeof(DWORD), &spaceUsed);
|
||||||
|
std::vector<std::pair<DWORD, std::optional<std::wstring>>> processes;
|
||||||
|
for (int i = 0; i < spaceUsed / sizeof(DWORD); ++i) processes.push_back({ processIds[i], Util::GetModuleFilename(processIds[i]) });
|
||||||
|
return processes;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::wstring> GetClipboardText()
|
||||||
|
{
|
||||||
|
if (!IsClipboardFormatAvailable(CF_UNICODETEXT)) return {};
|
||||||
|
if (!OpenClipboard(NULL)) return {};
|
||||||
|
|
||||||
|
std::optional<std::wstring> text;
|
||||||
|
if (AutoHandle<Functor<GlobalUnlock>> clipboard = GetClipboardData(CF_UNICODETEXT)) text = (wchar_t*)GlobalLock(clipboard);
|
||||||
|
CloseClipboard();
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::wstring> StringToWideString(const std::string& text, UINT encoding)
|
||||||
|
{
|
||||||
|
std::vector<wchar_t> buffer(text.size() + 1);
|
||||||
|
if (int length = MultiByteToWideChar(encoding, 0, text.c_str(), text.size() + 1, buffer.data(), buffer.size()))
|
||||||
|
return std::wstring(buffer.data(), length - 1);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HookParam> ParseCode(std::wstring code)
|
||||||
|
{
|
||||||
|
if (code[0] == L'/') code.erase(0, 1); // legacy/AGTH compatibility
|
||||||
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 {};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring Generate(HookParam hp, DWORD processId)
|
std::wstring GenerateCode(HookParam hp, DWORD processId)
|
||||||
{
|
{
|
||||||
return hp.type & DIRECT_READ ? GenerateRCode(hp) : GenerateHCode(hp, processId);
|
return hp.type & DIRECT_READ ? GenerateRCode(hp) : GenerateHCode(hp, processId);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(
|
TEST(
|
||||||
assert(StringToWideString(u8"こんにちは") == L"こんにちは"),
|
assert(StringToWideString(u8"こんにちは").value() == L"こんにちは"),
|
||||||
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(ParseCode(L"/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA")),
|
||||||
assert(Parse(L"/HQN936#-c*C:C*1C@4AA:gdi.dll:GetTextOutA /KF")),
|
assert(ParseCode(L"HB4@0")),
|
||||||
assert(Parse(L"HB4@0")),
|
assert(ParseCode(L"/RS65001#@44")),
|
||||||
assert(Parse(L"/RS65001#@44")),
|
assert(!ParseCode(L"HQ@4")),
|
||||||
assert(Parse(L"HQ@4")),
|
assert(!ParseCode(L"/RW@44")),
|
||||||
assert(!Parse(L"/RW@44")),
|
assert(!ParseCode(L"/HWG@33"))
|
||||||
assert(!Parse(L"/HWG@33"))
|
|
||||||
);
|
);
|
||||||
}
|
}
|
15
GUI/host/util.h
Normal file
15
GUI/host/util.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace Util
|
||||||
|
{
|
||||||
|
std::optional<std::wstring> GetModuleFilename(DWORD processId, HMODULE module = NULL);
|
||||||
|
std::optional<std::wstring> GetModuleFilename(HMODULE module = NULL);
|
||||||
|
std::vector<std::pair<DWORD, std::optional<std::wstring>>> GetAllProcesses();
|
||||||
|
std::optional<std::wstring> GetClipboardText();
|
||||||
|
std::optional<std::wstring> StringToWideString(const std::string& text, UINT encoding = CP_UTF8);
|
||||||
|
std::optional<HookParam> ParseCode(std::wstring code);
|
||||||
|
std::wstring GenerateCode(HookParam hp, DWORD processId = 0);
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "module.h"
|
#include "host/util.h"
|
||||||
#include <winhttp.h>
|
#include <winhttp.h>
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
extern const wchar_t* UPDATE_AVAILABLE;
|
extern const wchar_t* UPDATE_AVAILABLE;
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
}).detach();
|
}).detach();
|
||||||
|
|
||||||
QDir::setCurrent(QFileInfo(S(GetModuleFilename().value())).absolutePath());
|
QDir::setCurrent(QFileInfo(S(Util::GetModuleFilename().value())).absolutePath());
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
app.setFont(QFont("MS Shell Dlg 2", 10));
|
app.setFont(QFont("MS Shell Dlg 2", 10));
|
||||||
|
1132
GUI/mainwindow.cpp
1132
GUI/mainwindow.cpp
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,56 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
|
#include "extenwindow.h"
|
||||||
|
#include "host/host.h"
|
||||||
|
|
||||||
|
namespace Ui
|
||||||
|
{
|
||||||
|
class MainWindow;
|
||||||
|
}
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(QWidget *parent = nullptr);
|
explicit MainWindow(QWidget *parent = nullptr);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void closeEvent(QCloseEvent*);
|
inline static constexpr auto HOOK_SAVE_FILE = u8"SavedHooks.txt";
|
||||||
|
inline static constexpr auto GAME_SAVE_FILE = u8"SavedGames.txt";
|
||||||
|
|
||||||
|
void closeEvent(QCloseEvent*) override;
|
||||||
|
void ProcessConnected(DWORD processId);
|
||||||
|
void ProcessDisconnected(DWORD processId);
|
||||||
|
void ThreadAdded(TextThread& thread);
|
||||||
|
void ThreadRemoved(TextThread& thread);
|
||||||
|
bool SentenceReceived(TextThread& thread, std::wstring& sentence);
|
||||||
|
void OutputContextMenu(QPoint point);
|
||||||
|
QString TextThreadString(TextThread& thread);
|
||||||
|
ThreadParam ParseTextThreadString(QString ttString);
|
||||||
|
DWORD GetSelectedProcessId();
|
||||||
|
std::array<InfoForExtension, 10> GetSentenceInfo(TextThread& thread);
|
||||||
|
std::optional<std::wstring> UserSelectedProcess();
|
||||||
|
void AttachProcess();
|
||||||
|
void LaunchProcess();
|
||||||
|
void DetachProcess();
|
||||||
|
void ForgetProcess();
|
||||||
|
void AddHook();
|
||||||
|
void AddHook(QString hook);
|
||||||
|
void RemoveHooks();
|
||||||
|
void SaveHooks();
|
||||||
|
void FindHooks();
|
||||||
|
void Settings();
|
||||||
|
void Extensions();
|
||||||
|
void ViewThread(int index);
|
||||||
|
void SetOutputFont(QString font);
|
||||||
|
|
||||||
|
Ui::MainWindow* ui;
|
||||||
|
ExtenWindow* extenWindow;
|
||||||
|
std::unordered_set<DWORD> alreadyAttached;
|
||||||
|
bool autoAttach = false, autoAttachSavedOnly = true;
|
||||||
|
bool showSystemProcesses = false;
|
||||||
|
uint64_t savedThreadCtx = 0, savedThreadCtx2 = 0;
|
||||||
|
wchar_t savedThreadCode[1000] = {};
|
||||||
|
TextThread* current = nullptr;
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1000</width>
|
<width>900</width>
|
||||||
<height>600</height>
|
<height>600</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
@ -77,8 +77,8 @@
|
|||||||
<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</family>
|
||||||
<pointsize>13</pointsize>
|
<pointsize>12</pointsize>
|
||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
<property name="contextMenuPolicy">
|
<property name="contextMenuPolicy">
|
||||||
|
Binary file not shown.
33
README.md
33
README.md
@ -2,28 +2,30 @@
|
|||||||
|
|
||||||
![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_JP.md) ● [Русский](README_RU.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md) ● [ภาษาไทย](README_TH.md) ● [한국어](README_KR.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](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.
|
||||||
|
|
||||||
|
[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=akashmozumdar%40gmail.com&item_name=Textractor%20development¤cy_code=USD)
|
||||||
|
|
||||||
## Download
|
## Download
|
||||||
|
|
||||||
Official stable releases of Textractor can be found [here](https://github.com/Artikash/Textractor/releases).<br>
|
Releases of Textractor can be found [here](https://github.com/Artikash/Textractor/releases).<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
|
||||||
|
|
||||||
- Highly extensible and customizable
|
- Highly extensible and customizable
|
||||||
- Auto hook many game engines (including some not supported by VNR!)
|
- Auto hook many game engines (including some not supported by VNR!)
|
||||||
- Hook text using /H "hook" codes (most AGTH codes supported)
|
- Hook text using /H "hook" codes (most AGTH codes supported)
|
||||||
- Automatically search for possible hook codes
|
- Directly extract text using /R "read" codes
|
||||||
|
|
||||||
## 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 +34,22 @@ 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.
|
||||||
|
|
||||||
## 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 Visual Studio with CMake support, as well as Qt version 5.11<br>
|
||||||
You should then be able to just open the source folder in Visual Studio and build.
|
You should then be able to simply open the folder in Visual Studio, and build. Run Textractor.exe.
|
||||||
|
|
||||||
## 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)
|
||||||
|
52
README_DE.md
52
README_DE.md
@ -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)
|
|
@ -1,12 +1,12 @@
|
|||||||
# 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_JP.md) ● [Русский](README_RU.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md) ● [ภาษาไทย](README_TH.md) ● [한국어](README_KR.md)
|
||||||
|
|
||||||
## [Video tutorial](docs/TUTORIAL.md)
|
## [Video tutorial](https://tinyurl.com/textractor-tutorial)
|
||||||
|
|
||||||
## Descripción general
|
## Descripción general
|
||||||
|
|
||||||
**Textractor** (también conocido como NextHooker) es un extractor y traductor de texto de código abierto x86/x64 para Windows/Wine bassado en [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
|
**Textractor** (también conocido como NextHooker) es un extractor y traductor de texto de código abierto x86/x64 para Windows/Wine bassado en [ITHVNR](http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
|
||||||
|
|
||||||
![Cómo se ve](screenshot.png)
|
![Cómo se ve](screenshot.png)
|
||||||
|
|
||||||
|
51
README_FR.md
51
README_FR.md
@ -1,51 +0,0 @@
|
|||||||
# Textractor
|
|
||||||
|
|
||||||
![À 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)
|
|
||||||
|
|
||||||
**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.
|
|
||||||
|
|
||||||
## Téléchargement
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
## Nouveautés
|
|
||||||
- Hautement extensible et personnalisable
|
|
||||||
- Accrochage automatique de nombreux moteurs de jeu (dont certains non pris en charge par VNR!)
|
|
||||||
- Les textes de hook qui utilisent des /H "hook" codes (la plupart des codes AGTH pris en charge)
|
|
||||||
- Extraire directement le texte à l'aide des codes /R "lire"
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
Veuillez me signaler les bugs, les jeux dont Textractor a du mal à accrocher, les demandes de fonctionnalités ou d'autres suggestions. <br>
|
|
||||||
Si vous rencontrez des difficultés pour accrocher un jeu, veuillez m'envoyer un e-mail à un endroit où je peux le télécharger librement/gratuitement ou me le faire cadeau sur [Steam](https://steamcommunity.com/profiles/76561198097566313/).
|
|
||||||
|
|
||||||
## Extensions
|
|
||||||
|
|
||||||
Regarde mes [Example Extension project](https://github.com/Artikash/ExampleExtension) pour voir comment créer un extension.<br>
|
|
||||||
Voir le dossier des extensions pour des exemples de ce que les extensions peuvent faire.
|
|
||||||
|
|
||||||
## 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>
|
|
||||||
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.
|
|
||||||
|
|
||||||
## Compilation
|
|
||||||
|
|
||||||
Avant de compiler *Textractor*, vous devriez obtenir Visual Studio avec prise en charge de CMake, ainsi que Qt version 5.13 <br>
|
|
||||||
Vous devriez ensuite pouvoir simplement ouvrir le dossier dans Visual Studio et faire le build. Exécutez Textractor.exe.
|
|
||||||
|
|
||||||
## Architecture du projet
|
|
||||||
|
|
||||||
L'hôte (voir GUI / dossier hôte) injecte texthook.dll (créé à partir du dossier texthook) dans le processus cible et s'y connecte via 2 fichiers pipe. <br>
|
|
||||||
L'hôte écrit dans hostPipe, texthook écrit dans hookPipe. <br>
|
|
||||||
Texthook attend que le canal soit connecté, puis injecte quelques instructions dans toutes les fonctions de sortie de texte (par exemple TextOut, GetGlyphOutline) qui provoquent l'envoi de leur entrée via le canal. <br>
|
|
||||||
Des informations supplémentaires sur les hooks sont échangées via la mémoire partagée. <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.
|
|
||||||
|
|
||||||
## [Développeurs](docs/CREDITS.md)
|
|
42
README_ID.md
42
README_ID.md
@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
![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_JP.md) ● [Русский](README_RU.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md) ● [ภาษาไทย](README_TH.md) ● [한국어](README_KR.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](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.
|
||||||
|
|
||||||
|
[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=akashmozumdar%40gmail.com&item_name=Textractor%20development¤cy_code=USD)
|
||||||
|
|
||||||
## 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 +24,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.11<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)
|
||||||
|
52
README_IT.md
52
README_IT.md
@ -1,52 +0,0 @@
|
|||||||
# Textractor
|
|
||||||
|
|
||||||
![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)
|
|
||||||
|
|
||||||
**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.
|
|
||||||
|
|
||||||
## Scarica
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
## Caratteristiche
|
|
||||||
|
|
||||||
- Altamente estensibile e personalizzabile
|
|
||||||
- Aggancia automaticamente molti engine di gioco (inclusi alcuni non supportati da VNR!)
|
|
||||||
- Aggancia il testo utilizzando codici /H "hook" (molti codici AGTH supportati)
|
|
||||||
- Estrae il testo direttamente usando codici /R "read"
|
|
||||||
|
|
||||||
## Supporto
|
|
||||||
|
|
||||||
Fatemi sapere su qualunque bug, giochi che Textractor ha problemi nell'agganciare, richieste future, o altri suggerimenti.<br>
|
|
||||||
Se avete dei problemi nel agganciare un gioco vi prego di inviarmi via email un sito dove posso scaricarlo, o regalatemelo su [Steam](https://steamcommunity.com/profiles/76561198097566313/).
|
|
||||||
|
|
||||||
## Estenzioni
|
|
||||||
|
|
||||||
Guardate il mio [Progetto Example Extension](https://github.com/Artikash/ExampleExtension) per vedere come costruire un estenzione..<br>
|
|
||||||
Guardate la cartella delle estenzioni per esempi di cosa possono fare le estenzioni.
|
|
||||||
|
|
||||||
## Contributi
|
|
||||||
|
|
||||||
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>
|
|
||||||
Contribuire alla traduzione è semplice: traduci le stringhe in [text.cpp](text.cpp) cosi come questo README.
|
|
||||||
|
|
||||||
## Compiling
|
|
||||||
|
|
||||||
Prima di compilare *Textractor*, dovresti ottenere Visual Studio con supporto CMAKE, cosi come Qt versione 5.13<br>
|
|
||||||
Dovresti essere in grado di aprire la cartella in Visual Studio, e costruire. Avvia Textractor.exe
|
|
||||||
|
|
||||||
## 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 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>
|
|
||||||
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>
|
|
||||||
Infine, la GUI dispone il testo alle estenzioni prima di mostrarle.
|
|
||||||
|
|
||||||
## [Sviluppatori](docs/CREDITS.md)
|
|
@ -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_JP.md) ● [Русский](README_RU.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md) ● [ภาษาไทย](README_TH.md) ● [한국어](README_KR.md)
|
||||||
|
|
||||||
**Textractor** はビジュアルノベル文字抽出プログラム。
|
**Textractor** はビジュアルノベル文字抽出プログラム。
|
||||||
|
|
||||||
|
17
README_KR.md
17
README_KR.md
@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
![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_JP.md) ● [Русский](README_RU.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md) ● [ภาษาไทย](README_TH.md) ● [한국어](README_KR.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](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) 를 참고하세요.
|
||||||
|
|
||||||
|
[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=akashmozumdar%40gmail.com&item_name=Textractor%20development¤cy_code=USD)
|
||||||
|
|
||||||
## 다운로드
|
## 다운로드
|
||||||
|
|
||||||
[여기](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를 실행해 보시기 바랍니다.
|
||||||
|
|
||||||
## 특징
|
## 특징
|
||||||
|
|
||||||
@ -35,16 +38,16 @@
|
|||||||
|
|
||||||
## 컴파일링
|
## 컴파일링
|
||||||
|
|
||||||
*Textractor*를 컴파일링 하기 전에, Qt version 5.13과 CMake를 포함한 Visual Studio가 있어야 합니다.<br>
|
*Textractor*를 컴파일링 하기 전에, Qt version 5.11과 CMake를 포함한 Visual Studio가 있어야 합니다.<br>
|
||||||
그 이후로는, 단순히 Visual Studio를 통해 폴더를 열고 빌드하는 것으로 실행이 가능합니다.
|
그 이후로는, 단순히 Visual Studio를 통해 폴더를 열고 빌드하는 것으로 실행이 가능합니다.
|
||||||
|
|
||||||
## 프로젝트 아키텍쳐
|
## 프로젝트 아키텍쳐
|
||||||
|
|
||||||
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)
|
||||||
|
21
README_PT.md
21
README_PT.md
@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
![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_JP.md) ● [Русский](README_RU.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md) ● [ภาษาไทย](README_TH.md) ● [한국어](README_KR.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](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.
|
||||||
|
|
||||||
|
[![Doe](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=akashmozumdar%40gmail.com&item_name=Textractor%20development¤cy_code=USD)
|
||||||
|
|
||||||
## 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,22 +34,22 @@ 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
|
||||||
|
|
||||||
Antes de compilar o *Textractor* você deve ter o Visual Studio com suporte ao CMake, assim como o Qt versão 5.13.<br>
|
Antes de compilar o *Textractor* você deve ter o Visual Studio com suporte ao CMake, assim como o Qt versão 5.11.<br>
|
||||||
Você deverá então ser capaz de simplesmente abrir uma pasta no Visual Studio e build. Inicie Textractor.exe.
|
Você deverá então ser capaz de simplesmente abrir uma pasta no Visual Studio e build. Inicie Textractor.exe.
|
||||||
|
|
||||||
## 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)
|
||||||
|
21
README_RU.md
21
README_RU.md
@ -2,15 +2,18 @@
|
|||||||
|
|
||||||
![Как это выглядит](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_JP.md) ● [Русский](README_RU.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md) ● [ภาษาไทย](README_TH.md) ● [한국어](README_KR.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](http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
|
||||||
Смотреть [обучающее видео](docs/TUTORIAL.md) для быстрого ознакомления.
|
Смотреть [обучающее видео](https://tinyurl.com/textractor-tutorial) для быстрого ознакомления.
|
||||||
|
|
||||||
|
[![Задонатить автору](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=akashmozumdar%40gmail.com&item_name=Textractor%20development¤cy_code=USD)
|
||||||
|
|
||||||
## Загрузка
|
## Загрузка
|
||||||
|
|
||||||
Выпуски 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,22 +34,22 @@
|
|||||||
|
|
||||||
## Вклад
|
## Вклад
|
||||||
|
|
||||||
Любой вклад приветствуется! Пишите мне(автору) на 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.
|
||||||
|
|
||||||
## Компиляция
|
## Компиляция
|
||||||
|
|
||||||
Перед компиляцией *Textractor*, установите Visual Studio с поддержкой CMake, а также Qt версии 5.13<br>
|
Перед компиляцией *Textractor*, установите Visual Studio с поддержкой CMake, а также Qt версии 5.11<br>
|
||||||
Тогда вы сможете просто открыть и построить проект в Visual Studio. Запустите Textractor.exe.
|
Тогда вы сможете просто открыть и построить проект в Visual Studio. Запустите Textractor.exe.
|
||||||
|
|
||||||
## Архитектура проекта
|
## Архитектура проекта
|
||||||
|
|
||||||
Хост (смотрите папку 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)
|
||||||
|
14
README_SC.md
14
README_SC.md
@ -1,10 +1,10 @@
|
|||||||
# 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_JP.md) ● [Русский](README_RU.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md) ● [ภาษาไทย](README_TH.md) ● [한국어](README_KR.md)
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
**Textractor** (曾用名: NextHooker) 是一个基于 [ITHVNR](https://web.archive.org/web/20160202084144/http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine), 为 Windows/Wine 开发的开源 x86/x64 文本提取器.<br>
|
**Textractor** (曾用名: NextHooker) 是一个基于 [ITHVNR](http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine), 为 Windows/Wine 开发的开源 x86/x64 文本提取器.<br>
|
||||||
|
|
||||||
![它工作起来的样子](screenshot.png)
|
![它工作起来的样子](screenshot.png)
|
||||||
|
|
||||||
@ -28,22 +28,22 @@ 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 即可.
|
||||||
|
|
||||||
## 编译
|
## 编译
|
||||||
|
|
||||||
编译 *Textractor* 前, 你应当获取支持 CMake 的 Visual Studio, 以及 Qt 5.13 版.<br>
|
编译 *Textractor* 前, 你应当获取支持 CMake 的 Visual Studio, 以及 Qt 5.11 版.<br>
|
||||||
之后就可以使用 Visual Studio 打开文件夹, 然后构建. 运行 Textractor.exe.
|
之后就可以使用 Visual Studio 打开文件夹, 然后构建. 运行 Textractor.exe.
|
||||||
|
|
||||||
## 项目架构
|
## 项目架构
|
||||||
|
|
||||||
宿主 (位于 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)
|
||||||
|
16
README_TH.md
16
README_TH.md
@ -2,16 +2,20 @@
|
|||||||
|
|
||||||
![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_JP.md) ● [Русский](README_RU.md) ● [Bahasa](README_ID.md) ● [Português](README_PT.md) ● [ภาษาไทย](README_TH.md) ● [한국어](README_KR.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](http://www.hongfire.com/forum/showthread.php/438331-ITHVNR-ITH-with-the-VNR-engine).<br>
|
||||||
สามารถดูตัวอย่างวิธีการใช้งาน [วีดีโอตัวอย่างการใช้งาน](docs/TUTORIAL.md) เพื่อที่จะแสดงความเข้าใจคร่าวๆเกี่ยวกับโปรแกรม.
|
สามารถดูตัวอย่างวิธีการใช้งาน [วีดีโอตัวอย่างการใช้งาน](https://tinyurl.com/textractor-tutorial) เพื่อที่จะแสดงความเข้าใจคร่าวๆเกี่ยวกับโปรแกรม.
|
||||||
|
|
||||||
|
[![Donate](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=akashmozumdar%40gmail.com&item_name=Textractor%20development¤cy_code=USD)
|
||||||
|
|
||||||
## ดาวน์โหลด
|
## ดาวน์โหลด
|
||||||
|
|
||||||
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 +41,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 +49,4 @@ ITHVNR รุ่นสุดท้ายสามารถดาวน์โห
|
|||||||
ตัวอักษรที่ฐานโปรแกรมรับผ่านท่อจะถูกแปลงเล็กน้อยก่อนที่จะแสดงผ่าน GUI <br>
|
ตัวอักษรที่ฐานโปรแกรมรับผ่านท่อจะถูกแปลงเล็กน้อยก่อนที่จะแสดงผ่าน GUI <br>
|
||||||
สุดท้ายแล้ว GUI จะส่งข้อมูลตัวอักษรไปยังส่วนขยายต่างๆก่อนที่จะแสดงให้เห็นในหน้าจอ
|
สุดท้ายแล้ว GUI จะส่งข้อมูลตัวอักษรไปยังส่วนขยายต่างๆก่อนที่จะแสดงให้เห็นในหน้าจอ
|
||||||
|
|
||||||
## [นักพัฒนา](docs/CREDITS.md)
|
## [นักพัฒนา](CREDITS.md)
|
||||||
|
@ -1,84 +1,109 @@
|
|||||||
macro(msvc_registry_search)
|
macro(msvc_registry_search)
|
||||||
if(NOT DEFINED Qt5_DIR)
|
IF(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.*")
|
||||||
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
|
||||||
list(REVERSE QT_VERSIONS)
|
list(REVERSE QT_VERSIONS)
|
||||||
|
|
||||||
list(GET QT_VERSIONS 0 QT_VERSION)
|
list(GET QT_VERSIONS 0 QT_VERSION)
|
||||||
|
|
||||||
# fix any double slashes which seem to be common
|
# fix any double slashes which seem to be common
|
||||||
string(REPLACE "//" "/" QT_VERSION "${QT_VERSION}")
|
string(REPLACE "//" "/" QT_VERSION "${QT_VERSION}")
|
||||||
|
|
||||||
if(MSVC_VERSION GREATER_EQUAL 1920)
|
if(MSVC_VERSION GREATER_EQUAL "1910")
|
||||||
set(QT_MSVC 2019)
|
set(QT_MSVC "2017")
|
||||||
elseif(MSVC_VERSION GREATER_EQUAL 1910)
|
elseif(MSVC_VERSION GREATER_EQUAL "1900")
|
||||||
set(QT_MSVC 2017)
|
set(QT_MSVC "2015")
|
||||||
elseif(MSVC_VERSION GREATER_EQUAL 1900)
|
else()
|
||||||
set(QT_MSVC 2015)
|
# Latest QT versions >5.10 provides only 2015 and 2017 prebuilt binaries
|
||||||
else()
|
message(WARNING "Unsupported MSVC toolchain version")
|
||||||
message(WARNING "Unsupported MSVC toolchain version")
|
endif()
|
||||||
endif()
|
|
||||||
|
|
||||||
if(QT_MSVC)
|
if(QT_MSVC)
|
||||||
if(CMAKE_CL_64)
|
# check for 64-bit target
|
||||||
SET(QT_SUFFIX "_64")
|
if(CMAKE_CL_64)
|
||||||
else()
|
SET(QT_MSVC "${QT_MSVC}_64")
|
||||||
set(QT_SUFFIX "")
|
endif()
|
||||||
endif()
|
|
||||||
|
|
||||||
# MSVC 2015+ is only backwards compatible
|
set(QT_TOOLCHAIN "${QT_VERSION}/msvc${QT_MSVC}")
|
||||||
if(EXISTS "${QT_VERSION}/msvc${QT_MSVC}${QT_SUFFIX}")
|
if(EXISTS ${QT_TOOLCHAIN})
|
||||||
set(Qt5_DIR "${QT_VERSION}/msvc${QT_MSVC}${QT_SUFFIX}/lib/cmake/Qt5")
|
set(Qt5_DIR "${QT_TOOLCHAIN}/lib/cmake/Qt5")
|
||||||
elseif(QT_MSVC GREATER_EQUAL 2019 AND EXISTS "${QT_VERSION}/msvc2017${QT_SUFFIX}")
|
elseif(QT_MSVC EQUAL "2017")
|
||||||
set(Qt5_DIR "${QT_VERSION}/msvc2017${QT_SUFFIX}/lib/cmake/Qt5")
|
#2017 is ABI compatible with 2015
|
||||||
elseif(QT_MSVC GREATER_EQUAL 2017 AND EXISTS "${QT_VERSION}/msvc2015${QT_SUFFIX}")
|
if(CMAKE_CL_64)
|
||||||
set(Qt5_DIR "${QT_VERSION}/msvc2015${QT_SUFFIX}/lib/cmake/Qt5")
|
set(QT_TOOLCHAIN "${QT_VERSION}/msvc2015_64")
|
||||||
else()
|
else()
|
||||||
message(WARNING "Required QT5 toolchain is not installed")
|
set(QT_TOOLCHAIN "${QT_VERSION}/msvc2015")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
|
||||||
endif()
|
if(EXISTS ${QT_TOOLCHAIN})
|
||||||
|
set(Qt5_DIR "${QT_TOOLCHAIN}/lib/cmake/Qt5")
|
||||||
|
else()
|
||||||
|
message(WARNING "Required QT5 toolchain is not installed")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(WARNING "Required QT5 toolchain is not installed")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
ENDIF()
|
||||||
endmacro()
|
endmacro()
|
||||||
|
|
||||||
macro(find_qt5)
|
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)
|
||||||
if(WIN32 AND TARGET Qt5::qmake AND NOT TARGET Qt5::windeployqt)
|
if(WIN32 AND TARGET Qt5::qmake AND NOT TARGET Qt5::windeployqt)
|
||||||
get_target_property(_qt5_qmake_location Qt5::qmake IMPORTED_LOCATION)
|
get_target_property(_qt5_qmake_location Qt5::qmake IMPORTED_LOCATION)
|
||||||
|
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND "${_qt5_qmake_location}" -query QT_INSTALL_PREFIX
|
COMMAND "${_qt5_qmake_location}" -query QT_INSTALL_PREFIX
|
||||||
RESULT_VARIABLE return_code
|
RESULT_VARIABLE return_code
|
||||||
OUTPUT_VARIABLE qt5_install_prefix
|
OUTPUT_VARIABLE qt5_install_prefix
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
)
|
)
|
||||||
|
|
||||||
set(imported_location "${qt5_install_prefix}/bin/windeployqt.exe")
|
set(imported_location "${qt5_install_prefix}/bin/windeployqt.exe")
|
||||||
|
|
||||||
if(EXISTS ${imported_location})
|
if(EXISTS ${imported_location})
|
||||||
add_executable(Qt5::windeployqt IMPORTED)
|
add_executable(Qt5::windeployqt IMPORTED)
|
||||||
|
|
||||||
set_target_properties(Qt5::windeployqt PROPERTIES
|
set_target_properties(Qt5::windeployqt PROPERTIES
|
||||||
IMPORTED_LOCATION ${imported_location}
|
IMPORTED_LOCATION ${imported_location}
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Cannot find QT5!")
|
message(FATAL_ERROR "Cannot find QT5!")
|
||||||
endif()
|
endif()
|
||||||
endmacro(find_qt5)
|
endmacro(find_qt5)
|
||||||
|
|
||||||
|
# Copies required DLLs to directory with target
|
||||||
|
# Optionally can provide QML directory as second argument
|
||||||
|
function(install_qt5_libs target)
|
||||||
|
if(TARGET Qt5::windeployqt)
|
||||||
|
set(EXTRA "")
|
||||||
|
if(EXISTS ${ARGV1})
|
||||||
|
message("QML directory to be scanned=${ARGV1}")
|
||||||
|
list(APPEND EXTRA --qmldir ${ARGV1})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# execute windeployqt in a tmp directory after build
|
||||||
|
add_custom_command(TARGET ${target}
|
||||||
|
POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt"
|
||||||
|
COMMAND set PATH=%PATH%$<SEMICOLON>${qt5_install_prefix}/bin
|
||||||
|
COMMAND Qt5::windeployqt --dir $<TARGET_FILE_DIR:${target}> "$<TARGET_FILE_DIR:${target}>/$<TARGET_FILE_NAME:${target}>" ${EXTRA}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
endfunction(install_qt5_libs)
|
||||||
|
55
deploy.ps1
55
deploy.ps1
@ -1,8 +1,8 @@
|
|||||||
param([string]$version)
|
param([string]$version)
|
||||||
|
|
||||||
cd $PSScriptRoot;
|
cd $PSScriptRoot;
|
||||||
mkdir -Force -Verbose builds;
|
mkdir -Force -Verbose Builds;
|
||||||
cd builds;
|
cd Builds;
|
||||||
mkdir -Force -Verbose x86;
|
mkdir -Force -Verbose x86;
|
||||||
mkdir -Force -Verbose x64;
|
mkdir -Force -Verbose x64;
|
||||||
|
|
||||||
@ -16,8 +16,6 @@ foreach ($language in @{
|
|||||||
PORTUGUESE="Portuguese";
|
PORTUGUESE="Portuguese";
|
||||||
THAI="Thai";
|
THAI="Thai";
|
||||||
KOREAN="Korean";
|
KOREAN="Korean";
|
||||||
ITALIAN="Italian";
|
|
||||||
FRENCH="French"
|
|
||||||
}.GetEnumerator())
|
}.GetEnumerator())
|
||||||
{
|
{
|
||||||
$folder = "Textractor-$($language.Value)-$version";
|
$folder = "Textractor-$($language.Value)-$version";
|
||||||
@ -31,41 +29,31 @@ foreach ($language in @{
|
|||||||
&"C:\Program Files\CMake\bin\cmake" -G "Visual Studio 16 2019" -A"$VS_arch" -DVERSION="$version" -DTEXT_LANGUAGE="$($language.Key)" -DCMAKE_BUILD_TYPE="Release" ../..;
|
&"C:\Program Files\CMake\bin\cmake" -G "Visual Studio 16 2019" -A"$VS_arch" -DVERSION="$version" -DTEXT_LANGUAGE="$($language.Key)" -DCMAKE_BUILD_TYPE="Release" ../..;
|
||||||
&"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\devenv" Textractor.sln /build "Release|$VS_arch";
|
&"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\devenv" Textractor.sln /build "Release|$VS_arch";
|
||||||
cd ..;
|
cd ..;
|
||||||
|
&"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /v /t "http://timestamp.digicert.com" /fd SHA256 "Release_$arch/Textractor.exe";
|
||||||
|
&"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /v /t "http://timestamp.digicert.com" /fd SHA256 "Release_$arch/TextractorCLI.exe";
|
||||||
mkdir -Force -Verbose "$folder/$arch";
|
mkdir -Force -Verbose "$folder/$arch";
|
||||||
foreach ($file in @(
|
foreach ($file in @(
|
||||||
"Textractor.exe",
|
"Textractor.exe",
|
||||||
"TextractorCLI.exe",
|
"TextractorCLI.exe",
|
||||||
"texthook.dll"
|
"texthook.dll",
|
||||||
|
"Bing Translate.dll",
|
||||||
|
"Copy to Clipboard.dll",
|
||||||
|
"Extra Newlines.dll",
|
||||||
|
"Extra Window.dll",
|
||||||
|
"Google Translate.dll",
|
||||||
|
"Lua.dll",
|
||||||
|
"Regex Filter.dll",
|
||||||
|
"Remove Repeated Characters.dll",
|
||||||
|
"Remove Repeated Phrases.dll",
|
||||||
|
"Remove Repeated Phrases 2.dll",
|
||||||
|
"Remove 30 Repeated Sentences.dll",
|
||||||
|
"Replacer.dll",
|
||||||
|
"Thread Linker.dll"
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
copy -Force -Recurse -Verbose -Destination "$folder/$arch" -Path "Release_$arch/$file";
|
copy -Force -Recurse -Verbose -Destination "$folder/$arch" -Path "Release_$arch/$file";
|
||||||
}
|
}
|
||||||
foreach ($extension in @(
|
|
||||||
"Bing Translate",
|
|
||||||
"Copy to Clipboard",
|
|
||||||
"DeepL Translate",
|
|
||||||
"DevTools DeepL Translate",
|
|
||||||
"DevTools Papago Translate",
|
|
||||||
"DevTools Systran Translate",
|
|
||||||
"Extra Newlines",
|
|
||||||
"Extra Window",
|
|
||||||
"Google Translate",
|
|
||||||
"Lua",
|
|
||||||
"Regex Filter",
|
|
||||||
"Regex Replacer",
|
|
||||||
"Remove Repeated Characters",
|
|
||||||
"Remove Repeated Phrases",
|
|
||||||
"Remove Repeated Phrases 2",
|
|
||||||
"Remove 30 Repeated Sentences",
|
|
||||||
"Replacer",
|
|
||||||
"Styler",
|
|
||||||
"Thread Linker"
|
|
||||||
))
|
|
||||||
{
|
|
||||||
copy -Force -Recurse -Verbose -Destination "$folder/$arch/$extension.xdll" -Path "Release_$arch/$extension.dll";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
&"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";
|
||||||
@ -78,9 +66,6 @@ foreach ($arch in @("x86", "x64"))
|
|||||||
"LocaleEmulator.dll",
|
"LocaleEmulator.dll",
|
||||||
"Qt5Core.dll",
|
"Qt5Core.dll",
|
||||||
"Qt5Gui.dll",
|
"Qt5Gui.dll",
|
||||||
"Qt5Network.dll",
|
|
||||||
"Qt5WebSockets.dll",
|
|
||||||
"Qt5WinExtras.dll"
|
|
||||||
"Qt5Widgets.dll",
|
"Qt5Widgets.dll",
|
||||||
"platforms",
|
"platforms",
|
||||||
"styles"
|
"styles"
|
||||||
@ -88,14 +73,12 @@ 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/*"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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;
|
||||||
&"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /v /t "http://timestamp.digicert.com" /fd SHA256 "Builds/Textractor-$version-Setup.exe";
|
&"C:\Program Files (x86)\Windows Kits\10\App Certification Kit\signtool.exe" sign /a /v /t "http://timestamp.digicert.com" /fd SHA256 "Builds/Textractor-$version-Setup.exe";
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
# Developers
|
|
||||||
|
|
||||||
- Textractor mainly made by [Artikash](https://github.com/Artikash) with the contributions of
|
|
||||||
- Build system improvements by [DoumanAsh](https://github.com/DoumanAsh)
|
|
||||||
- Original text removal in Extra Window by [Niakr1s](https://github.com/Niakr1s)
|
|
||||||
- CLI bugfixes by [tinyAdapter](https://github.com/tinyAdapter)
|
|
||||||
- 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 ShinyDays and Artemis by [luojunyuan](https://github.com/luojunyuan)
|
|
||||||
- Google Chrome DevTools integration by [zeheyler](https://github.com/zeheyler)
|
|
||||||
- AppVeyor setup by [silas1037](https://github.com/silas1037)
|
|
||||||
- Improved attach dialog by [tera8m4](https://github.com/tera8m4)
|
|
||||||
- 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)
|
|
||||||
- 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)
|
|
||||||
- Russian translation by [TokcDK](https://github.com/TokcDK)
|
|
||||||
- Indonesian translation by [Hawxone](https://github.com/Hawxone)
|
|
||||||
- Portuguese translation by [TsumiHokiro](https://github.com/TsumiHokiro)
|
|
||||||
- Thai translation by [azmadoppler](https://github.com/azmadoppler)
|
|
||||||
- Korean translation by [O SK](mailto:afkl11@outlook.kr)
|
|
||||||
- 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 originally made by [Stomp](mailto:zorkzero@hotmail.com)
|
|
||||||
- VNR engine made by [jichi](https://github.com/jichifly)
|
|
||||||
- ITH updated by [Andys](https://github.com/AndyScull)
|
|
||||||
- ITH originally made by [kaosu](https://code.google.com/archive/p/interactive-text-hooker)
|
|
||||||
|
|
||||||
If you're on this list and want your link changed let Artikash know.
|
|
||||||
|
|
||||||
# THANK YOU!!
|
|
@ -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.
|
|
@ -1,64 +1,26 @@
|
|||||||
|
include(QtUtils)
|
||||||
|
msvc_registry_search()
|
||||||
|
find_qt5(Core Widgets)
|
||||||
|
|
||||||
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(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(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)
|
||||||
add_library(Remove\ 30\ Repeated\ Sentences MODULE removerepeatsentence.cpp extensionimpl.cpp)
|
add_library(Remove\ 30\ Repeated\ Sentences MODULE removerepeatsentence.cpp extensionimpl.cpp)
|
||||||
add_library(Replacer MODULE replacer.cpp extensionimpl.cpp)
|
add_library(Replacer MODULE replacer.cpp extensionimpl.cpp)
|
||||||
add_library(Styler MODULE styler.cpp extensionimpl.cpp)
|
|
||||||
add_library(Thread\ Linker MODULE threadlinker.cpp extensionimpl.cpp)
|
add_library(Thread\ Linker MODULE threadlinker.cpp extensionimpl.cpp)
|
||||||
|
|
||||||
target_precompile_headers(Bing\ Translate REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Copy\ to\ Clipboard REUSE_FROM pch)
|
|
||||||
target_precompile_headers(DeepL\ Translate REUSE_FROM pch)
|
|
||||||
target_precompile_headers(DevTools\ DeepL\ Translate REUSE_FROM pch)
|
|
||||||
target_precompile_headers(DevTools\ Papago\ Translate REUSE_FROM pch)
|
|
||||||
target_precompile_headers(DevTools\ Systran\ Translate REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Extra\ Newlines REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Extra\ Window REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Google\ Translate REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Lua 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\ Phrases REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Remove\ Repeated\ Phrases\ 2 REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Remove\ 30\ Repeated\ Sentences REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Replacer REUSE_FROM pch)
|
|
||||||
target_precompile_headers(Styler REUSE_FROM pch)
|
|
||||||
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(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(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(Thread\ Linker Qt5::Widgets)
|
target_link_libraries(Thread\ Linker Qt5::Widgets)
|
||||||
|
|
||||||
add_custom_target(Cleaner ALL COMMAND del *.xdll WORKING_DIRECTORY ${CMAKE_FINAL_OUTPUT_DIRECTORY})
|
|
||||||
|
|
||||||
if (NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5WebSockets.dll AND NOT EXISTS ${CMAKE_FINAL_OUTPUT_DIRECTORY}/Qt5WebSocketsd.dll)
|
|
||||||
add_custom_command(TARGET DevTools\ DeepL\ Translate
|
|
||||||
POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E remove_directory "${CMAKE_CURRENT_BINARY_DIR}/windeployqt"
|
|
||||||
COMMAND set PATH=%PATH%$<SEMICOLON>${qt5_install_prefix}/bin
|
|
||||||
COMMAND Qt5::windeployqt --dir ${CMAKE_FINAL_OUTPUT_DIRECTORY} "${CMAKE_FINAL_OUTPUT_DIRECTORY}/DevTools\ DeepL\ Translate.dll"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
@ -1,241 +1,71 @@
|
|||||||
#include "qtcommon.h"
|
#include "extension.h"
|
||||||
#include "translatewrapper.h"
|
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
extern const wchar_t* TRANSLATION_ERROR;
|
extern const wchar_t* TRANSLATION_ERROR;
|
||||||
|
|
||||||
const char* TRANSLATION_PROVIDER = "Bing Translate";
|
extern Synchronized<std::wstring> translateTo;
|
||||||
const char* GET_API_KEY_FROM = "https://www.microsoft.com/en-us/translator/business/trial/#get-started";
|
|
||||||
extern const QStringList languagesTo
|
const char* TRANSLATION_PROVIDER = "Bing";
|
||||||
|
QStringList languages
|
||||||
{
|
{
|
||||||
"Afrikaans",
|
"English: en",
|
||||||
"Albanian",
|
"Arabic: ar",
|
||||||
"Amharic",
|
"Bosnian: bs-Latn",
|
||||||
"Arabic",
|
"Bulgarian: bg",
|
||||||
"Armenian",
|
"Catalan: ca",
|
||||||
"Assamese",
|
"Chinese(Simplified): zh-CHS",
|
||||||
"Azerbaijani",
|
"Chinese(Traditional): zh-CHT",
|
||||||
"Bangla",
|
"Croatian: hr",
|
||||||
"Bosnian (Latin)",
|
"Czech: cs",
|
||||||
"Bulgarian",
|
"Danish: da",
|
||||||
"Cantonese (Traditional)",
|
"Dutch: nl",
|
||||||
"Catalan",
|
"Estonian: et",
|
||||||
"Chinese (Simplified)",
|
"Finnish: fi",
|
||||||
"Chinese (Traditional)",
|
"French: fr",
|
||||||
"Croatian",
|
"German: de",
|
||||||
"Czech",
|
"Greek: el",
|
||||||
"Danish",
|
"Hebrew: he",
|
||||||
"Dari",
|
"Hindi: hi",
|
||||||
"Dutch",
|
"Hungarian: hu",
|
||||||
"English",
|
"Indonesian: id",
|
||||||
"Estonian",
|
"Italian: it",
|
||||||
"Fijian",
|
"Japanese: ja",
|
||||||
"Filipino",
|
"Klingon: tlh",
|
||||||
"Finnish",
|
"Korean: ko",
|
||||||
"French",
|
"Latvian: lv",
|
||||||
"French (Canada)",
|
"Lithuanian: lt",
|
||||||
"German",
|
"Malay: ms",
|
||||||
"Greek",
|
"Maltese: mt",
|
||||||
"Gujarati",
|
"Norwegian: no",
|
||||||
"Haitian Creole",
|
"Persian: fa",
|
||||||
"Hebrew",
|
"Polish: pl",
|
||||||
"Hindi",
|
"Portuguese: pt",
|
||||||
"Hmong Daw",
|
"Romanian: ro",
|
||||||
"Hungarian",
|
"Russian: ru",
|
||||||
"Icelandic",
|
"Serbian: sr-Cyrl",
|
||||||
"Indonesian",
|
"Slovak: sk",
|
||||||
"Inuktitut",
|
"Slovenian: sl",
|
||||||
"Irish",
|
"Spanish: es",
|
||||||
"Italian",
|
"Swedish: sv",
|
||||||
"Japanese",
|
"Thai: th",
|
||||||
"Kannada",
|
"Turkish: tr",
|
||||||
"Kazakh",
|
"Ukranian: uk",
|
||||||
"Khmer",
|
"Urdu: ur",
|
||||||
"Klingon",
|
"Vietnamese: vi",
|
||||||
"Korean",
|
"Welsh: cy"
|
||||||
"Kurdish (Central)",
|
|
||||||
"Kurdish (Northern)",
|
|
||||||
"Lao",
|
|
||||||
"Latvian",
|
|
||||||
"Lithuanian",
|
|
||||||
"Malagasy",
|
|
||||||
"Malay",
|
|
||||||
"Malayalam",
|
|
||||||
"Maltese",
|
|
||||||
"Maori",
|
|
||||||
"Marathi",
|
|
||||||
"Myanmar",
|
|
||||||
"Nepali",
|
|
||||||
"Norwegian",
|
|
||||||
"Odia",
|
|
||||||
"Pashto",
|
|
||||||
"Persian",
|
|
||||||
"Polish",
|
|
||||||
"Portuguese (Brazil)",
|
|
||||||
"Portuguese (Portugal)",
|
|
||||||
"Punjabi",
|
|
||||||
"Queretaro Otomi",
|
|
||||||
"Romanian",
|
|
||||||
"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" } }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000;
|
|
||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
|
|
||||||
{
|
{
|
||||||
if (!tlp.authKey.empty())
|
|
||||||
{
|
|
||||||
std::wstring translateFromComponent = tlp.translateFrom == L"?" ? L"" : L"&from=" + codes.at(tlp.translateFrom);
|
|
||||||
if (HttpRequest httpRequest{
|
|
||||||
L"Mozilla/5.0 Textractor",
|
|
||||||
L"api.cognitive.microsofttranslator.com",
|
|
||||||
L"POST",
|
|
||||||
FormatString(L"/translate?api-version=3.0&to=%s%s", codes.at(tlp.translateTo), translateFromComponent).c_str(),
|
|
||||||
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()
|
|
||||||
})
|
|
||||||
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 (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=auto-detect&to=%s&text=%s", translateTo->c_str(), Escape(text)).c_str()
|
||||||
})
|
})
|
||||||
if (auto translation = Copy(JSON::Parse(httpRequest.response)[0][L"translations"][0][L"text"].String())) return { true, translation.value() };
|
// Response formatted as JSON: translation starts with text":" and ends with ","to
|
||||||
else return { false, FormatString(L"%s (token=%s): %s", TRANSLATION_ERROR, std::exchange(token.Acquire().contents, L""), httpRequest.response) };
|
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"text\":\"(.+)\"\\,\"to"))) return { true, results[1] };
|
||||||
|
else return { false, TRANSLATION_ERROR };
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
}
|
}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <istream>
|
|
||||||
|
|
||||||
template <typename C, int delimiterCount, int blockSize = 0x1000 / sizeof(C)> // windows file block size
|
|
||||||
class BlockMarkupIterator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
BlockMarkupIterator(const std::istream& stream, const std::basic_string_view<C>(&delimiters)[delimiterCount]) : streambuf(*stream.rdbuf())
|
|
||||||
{
|
|
||||||
std::copy_n(delimiters, delimiterCount, this->delimiters.begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::array<std::basic_string<C>, delimiterCount>> Next()
|
|
||||||
{
|
|
||||||
std::array<std::basic_string<C>, delimiterCount> results;
|
|
||||||
Find(delimiters[0], true);
|
|
||||||
for (int i = 0; i < delimiterCount; ++i)
|
|
||||||
{
|
|
||||||
const auto delimiter = i + 1 < delimiterCount ? delimiters[i + 1] : end;
|
|
||||||
if (auto found = Find(delimiter, false)) results[i] = std::move(found.value());
|
|
||||||
else return {};
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::optional<std::basic_string<C>> Find(std::basic_string_view<C> delimiter, bool discard)
|
|
||||||
{
|
|
||||||
for (int i = 0; ;)
|
|
||||||
{
|
|
||||||
int pos = buffer.find(delimiter, i);
|
|
||||||
if (pos != std::string::npos)
|
|
||||||
{
|
|
||||||
auto result = !discard ? std::optional(std::basic_string(buffer.begin(), buffer.begin() + pos)) : std::nullopt;
|
|
||||||
buffer.erase(buffer.begin(), buffer.begin() + pos + delimiter.size());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int oldSize = buffer.size();
|
|
||||||
buffer.resize(oldSize + blockSize);
|
|
||||||
if (!streambuf.sgetn((char*)(buffer.data() + oldSize), blockSize * sizeof(C))) return {};
|
|
||||||
i = max(0, oldSize - (int)delimiter.size());
|
|
||||||
if (discard)
|
|
||||||
{
|
|
||||||
buffer.erase(0, i);
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr C endImpl[5] = { '|', 'E', 'N', 'D', '|' };
|
|
||||||
static constexpr std::basic_string_view<C> end{ endImpl, 5 };
|
|
||||||
|
|
||||||
std::basic_streambuf<char>& streambuf;
|
|
||||||
std::basic_string<C> buffer;
|
|
||||||
std::array<std::basic_string_view<C>, delimiterCount> delimiters;
|
|
||||||
};
|
|
10
extensions/blockmarkuplanguage.h
Normal file
10
extensions/blockmarkuplanguage.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
class BlockMarkupLanguageIterator
|
||||||
|
{
|
||||||
|
std::istreambuf_iterator<char> it;
|
||||||
|
};
|
31
extensions/charstorage.h
Normal file
31
extensions/charstorage.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
template <typename C>
|
||||||
|
class CharStorage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CharStorage(size_t capacity = 0)
|
||||||
|
{
|
||||||
|
storage.reserve(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Store(const std::basic_string<C>& string)
|
||||||
|
{
|
||||||
|
return storage.insert(storage.end(), string.c_str(), string.c_str() + string.size() + 1) - storage.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FreeExcess()
|
||||||
|
{
|
||||||
|
storage.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const C* Retrieve(int handle) const
|
||||||
|
{
|
||||||
|
return storage.data() + handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<C> storage;
|
||||||
|
};
|
@ -1,180 +0,0 @@
|
|||||||
#include "qtcommon.h"
|
|
||||||
#include "translatewrapper.h"
|
|
||||||
#include "network.h"
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
extern const wchar_t* TRANSLATION_ERROR;
|
|
||||||
|
|
||||||
const char* TRANSLATION_PROVIDER = "DeepL Translate";
|
|
||||||
const char* GET_API_KEY_FROM = "https://www.deepl.com/pro.html#developer";
|
|
||||||
extern const QStringList languagesTo
|
|
||||||
{
|
|
||||||
"Bulgarian",
|
|
||||||
"Chinese (Simplified)",
|
|
||||||
"Czech",
|
|
||||||
"Danish",
|
|
||||||
"Dutch",
|
|
||||||
"English (American)",
|
|
||||||
"English (British)",
|
|
||||||
"Estonian",
|
|
||||||
"Finnish",
|
|
||||||
"French",
|
|
||||||
"German",
|
|
||||||
"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" } }
|
|
||||||
};
|
|
||||||
|
|
||||||
bool translateSelectedOnly = true, useRateLimiter = true, rateLimitSelected = true, useCache = true, useFilter = true;
|
|
||||||
int tokenCount = 10, rateLimitTimespan = 60000, maxSentenceSize = 1000;
|
|
||||||
|
|
||||||
enum KeyType { CAT, REST };
|
|
||||||
int keyType = REST;
|
|
||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
|
|
||||||
{
|
|
||||||
if (!tlp.authKey.empty())
|
|
||||||
{
|
|
||||||
std::string translateFromComponent = tlp.translateFrom == L"?" ? "" : "&source_lang=" + WideStringToString(codes.at(tlp.translateFrom));
|
|
||||||
if (HttpRequest httpRequest{
|
|
||||||
L"Mozilla/5.0 Textractor",
|
|
||||||
tlp.authKey.find(L":fx") == std::string::npos ? L"api.deepl.com" : L"api-free.deepl.com",
|
|
||||||
L"POST",
|
|
||||||
keyType == CAT ? L"/v1/translate" : L"/v2/translate",
|
|
||||||
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), tlp.authKey, codes.at(tlp.translateTo)) + translateFromComponent,
|
|
||||||
L"Content-Type: application/x-www-form-urlencoded"
|
|
||||||
}; httpRequest && (httpRequest.response.find(L"translations") != std::string::npos || (httpRequest = HttpRequest{
|
|
||||||
L"Mozilla/5.0 Textractor",
|
|
||||||
tlp.authKey.find(L":fx") == std::string::npos ? L"api.deepl.com" : L"api-free.deepl.com",
|
|
||||||
L"POST",
|
|
||||||
(keyType = !keyType) == CAT ? L"/v1/translate" : L"/v2/translate",
|
|
||||||
FormatString("text=%S&auth_key=%S&target_lang=%S", Escape(text), tlp.authKey, codes.at(tlp.translateTo)) + translateFromComponent,
|
|
||||||
L"Content-Type: application/x-www-form-urlencoded"
|
|
||||||
})))
|
|
||||||
// Response formatted as JSON: translation starts with text":" and ends with "}]
|
|
||||||
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"translations"][0][L"text"].String())) return { true, translation.value() };
|
|
||||||
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
// user_preferred_langs? what should priority be? does timestamp do anything? other translation quality options?
|
|
||||||
auto body = FormatString(R"(
|
|
||||||
{
|
|
||||||
"id": %d,
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"method": "LMT_handle_jobs",
|
|
||||||
"params": {
|
|
||||||
"priority": -1,
|
|
||||||
"timestamp": %lld,
|
|
||||||
"lang": {
|
|
||||||
"target_lang": "%.2S",
|
|
||||||
"source_lang_user_selected": "%S"
|
|
||||||
},
|
|
||||||
"jobs": [{
|
|
||||||
"raw_en_sentence": "%s",
|
|
||||||
"raw_en_context_before": [],
|
|
||||||
"kind": "default",
|
|
||||||
"preferred_num_beams": 1,
|
|
||||||
"quality": "fast",
|
|
||||||
"raw_en_context_after": []
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)", id, r + (n - r % n), codes.at(tlp.translateTo), codes.at(tlp.translateFrom), JSON::Escape(WideStringToString(text)));
|
|
||||||
// missing accept-encoding header since it fucks up HttpRequest
|
|
||||||
if (HttpRequest httpRequest{
|
|
||||||
L"Mozilla/5.0 Textractor",
|
|
||||||
L"www2.deepl.com",
|
|
||||||
L"POST",
|
|
||||||
L"/jsonrpc",
|
|
||||||
body,
|
|
||||||
L"Host: www2.deepl.com\r\nAccept-Language: en-US,en;q=0.5\r\nContent-type: application/json; charset=utf-8\r\nOrigin: https://www.deepl.com\r\nTE: Trailers",
|
|
||||||
INTERNET_DEFAULT_PORT,
|
|
||||||
L"https://www.deepl.com/translator",
|
|
||||||
WINHTTP_FLAG_SECURE
|
|
||||||
})
|
|
||||||
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"result"][L"translations"][0][L"beams"][0][L"postprocessed_sentence"].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) };
|
|
||||||
}
|
|
@ -1,174 +0,0 @@
|
|||||||
#include "devtools.h"
|
|
||||||
#include "module.h"
|
|
||||||
#include <ppltasks.h>
|
|
||||||
#include <ShlObj.h>
|
|
||||||
#include <QWebSocket>
|
|
||||||
#include <QMetaEnum>
|
|
||||||
#include <QFileDialog>
|
|
||||||
#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
|
|
||||||
{
|
|
||||||
QLabel* statusLabel;
|
|
||||||
AutoHandle<> process = NULL;
|
|
||||||
QWebSocket webSocket;
|
|
||||||
std::atomic<int> idCounter = 0;
|
|
||||||
Synchronized<std::unordered_map<int, concurrency::task_completion_event<JSON::Value<wchar_t>>>> mapQueue;
|
|
||||||
|
|
||||||
void StatusChanged(QString status)
|
|
||||||
{
|
|
||||||
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 _ = ([]
|
|
||||||
{
|
|
||||||
QObject::connect(&webSocket, &QWebSocket::stateChanged,
|
|
||||||
[](QAbstractSocket::SocketState state) { StatusChanged(QMetaEnum::fromType<QAbstractSocket::SocketState>().valueToKey(state)); });
|
|
||||||
QObject::connect(&webSocket, &QWebSocket::textMessageReceived, [](QString message)
|
|
||||||
{
|
|
||||||
auto result = JSON::Parse(S(message));
|
|
||||||
auto mapQueue = ::mapQueue.Acquire();
|
|
||||||
if (auto id = result[L"id"].Number()) if (auto request = mapQueue->find((int)*id); request != mapQueue->end())
|
|
||||||
{
|
|
||||||
request->second.set(result);
|
|
||||||
mapQueue->erase(request);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace DevTools
|
|
||||||
{
|
|
||||||
void Initialize()
|
|
||||||
{
|
|
||||||
QString chromePath = settings.value(CHROME_LOCATION).toString();
|
|
||||||
if (chromePath.isEmpty())
|
|
||||||
{
|
|
||||||
for (auto [_, process] : GetAllProcesses())
|
|
||||||
if (process && (process->find(L"\\chrome.exe") != std::string::npos || process->find(L"\\msedge.exe") != std::string::npos)) chromePath = S(process.value());
|
|
||||||
wchar_t programFiles[MAX_PATH + 100] = {};
|
|
||||||
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, "/", "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()
|
|
||||||
{
|
|
||||||
webSocket.close();
|
|
||||||
for (const auto& [_, task] : mapQueue.Acquire().contents) task.set_exception(std::runtime_error("closed"));
|
|
||||||
mapQueue->clear();
|
|
||||||
|
|
||||||
if (process)
|
|
||||||
{
|
|
||||||
TerminateProcess(process, 0);
|
|
||||||
WaitForSingleObject(process, 1000);
|
|
||||||
for (int retry = 0; ++retry < 20; Sleep(100))
|
|
||||||
try { std::filesystem::remove_all(L"devtoolscache"); break; }
|
|
||||||
catch (std::filesystem::filesystem_error) { continue; }
|
|
||||||
}
|
|
||||||
process = NULL;
|
|
||||||
StatusChanged("Stopped");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Connected()
|
|
||||||
{
|
|
||||||
return webSocket.state() == QAbstractSocket::ConnectedState;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSON::Value<wchar_t> SendRequest(const char* method, const std::wstring& params)
|
|
||||||
{
|
|
||||||
concurrency::task_completion_event<JSON::Value<wchar_t>> response;
|
|
||||||
int id = idCounter += 1;
|
|
||||||
if (!Connected()) return {};
|
|
||||||
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))));
|
|
||||||
try { if (auto result = create_task(response).get()[L"result"]) return result; } catch (...) {}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
#include "qtcommon.h"
|
|
||||||
#include "network.h"
|
|
||||||
|
|
||||||
namespace DevTools
|
|
||||||
{
|
|
||||||
void Initialize();
|
|
||||||
void Close();
|
|
||||||
bool Connected();
|
|
||||||
JSON::Value<wchar_t> SendRequest(const char* method, const std::wstring& params = L"{}");
|
|
||||||
}
|
|
@ -1,148 +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 DeepL Translate";
|
|
||||||
const char* GET_API_KEY_FROM = nullptr;
|
|
||||||
|
|
||||||
extern const QStringList languagesTo
|
|
||||||
{
|
|
||||||
"Bulgarian",
|
|
||||||
"Chinese (Simplified)",
|
|
||||||
"Czech",
|
|
||||||
"Danish",
|
|
||||||
"Dutch",
|
|
||||||
"English (American)",
|
|
||||||
"English (British)",
|
|
||||||
"Estonian",
|
|
||||||
"Finnish",
|
|
||||||
"French",
|
|
||||||
"German",
|
|
||||||
"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
|
|
||||||
{
|
|
||||||
{ { 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)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
std::wstring escaped; // DeepL breaks with slash in input
|
|
||||||
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":"
|
|
||||||
document.querySelector('.lmt__language_select--source').querySelector('button').click();
|
|
||||||
document.evaluate(`//*[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click();
|
|
||||||
document.querySelector('.lmt__language_select--target').querySelector('button').click();
|
|
||||||
document.evaluate(`//*[text()='%s']`,document.querySelector('.lmt__language_select__menu'),null,XPathResult.FIRST_ORDERED_NODE_TYPE,null).singleNodeValue.click();
|
|
||||||
"})", codes.at(tlp.translateFrom), codes.at(tlp.translateTo)));
|
|
||||||
|
|
||||||
for (int retry = 0; ++retry < 100; Sleep(100))
|
|
||||||
if (auto translation = Copy(DevTools::SendRequest("Runtime.evaluate",
|
|
||||||
LR"({"expression":"document.querySelector('#target-dummydiv').innerHTML.trim() ","returnByValue":true})"
|
|
||||||
)[L"result"][L"value"].String())) if (!translation->empty()) return { true, translation.value() };
|
|
||||||
if (auto errorMessage = Copy(DevTools::SendRequest("Runtime.evaluate",
|
|
||||||
LR"({"expression":"document.querySelector('div.lmt__system_notification').innerHTML","returnByValue":true})"
|
|
||||||
)[L"result"][L"value"].String())) return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, errorMessage.value()) };
|
|
||||||
return { false, TRANSLATION_ERROR };
|
|
||||||
}
|
|
@ -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 };
|
|
||||||
}
|
|
@ -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 };
|
|
||||||
}
|
|
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
struct InfoForExtension
|
struct InfoForExtension
|
||||||
{
|
{
|
||||||
const char* name;
|
const char* name;
|
||||||
@ -9,7 +11,7 @@ struct InfoForExtension
|
|||||||
struct SentenceInfo
|
struct SentenceInfo
|
||||||
{
|
{
|
||||||
const InfoForExtension* infoArray;
|
const InfoForExtension* infoArray;
|
||||||
int64_t operator[](std::string_view propertyName)
|
int64_t operator[](std::string propertyName)
|
||||||
{
|
{
|
||||||
for (auto info = infoArray; info->name; ++info) // nullptr name marks end of info array
|
for (auto info = infoArray; info->name; ++info) // nullptr name marks end of info array
|
||||||
if (propertyName == info->name) return info->value;
|
if (propertyName == info->name) return info->value;
|
||||||
|
@ -17,12 +17,12 @@ extern "C" __declspec(dllexport) wchar_t* OnNewSentence(wchar_t* sentence, const
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::wstring sentenceCopy(sentence);
|
std::wstring sentenceStr(sentence);
|
||||||
int oldSize = sentenceCopy.size();
|
int origLength = sentenceStr.size();
|
||||||
if (ProcessSentence(sentenceCopy, SentenceInfo{ sentenceInfo }))
|
if (ProcessSentence(sentenceStr, SentenceInfo{ sentenceInfo }))
|
||||||
{
|
{
|
||||||
if (sentenceCopy.size() > oldSize) sentence = (wchar_t*)HeapReAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sentence, (sentenceCopy.size() + 1) * sizeof(wchar_t));
|
if (sentenceStr.size() > origLength) sentence = (wchar_t*)HeapReAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sentence, (sentenceStr.size() + 1) * sizeof(wchar_t));
|
||||||
wcscpy_s(sentence, sentenceCopy.size() + 1, sentenceCopy.c_str());
|
wcscpy_s(sentence, sentenceStr.size() + 1, sentenceStr.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (SKIP)
|
catch (SKIP)
|
||||||
|
@ -1,32 +1,25 @@
|
|||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
#include "extension.h"
|
#include "extension.h"
|
||||||
#include "ui_extrawindow.h"
|
#include "ui_extrawindow.h"
|
||||||
#include "blockmarkup.h"
|
#include "defs.h"
|
||||||
|
#include "blockmarkuplanguage.h"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
#include <process.h>
|
#include <process.h>
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <QColorDialog>
|
#include <QColorDialog>
|
||||||
#include <QFontDialog>
|
#include <QFontDialog>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QGraphicsEffect>
|
#include <QGraphicsEffect>
|
||||||
#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* 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 +29,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)
|
||||||
{
|
{
|
||||||
@ -47,35 +40,29 @@ QColor colorPrompt(QWidget* parent, QColor default, const QString& title, bool c
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PrettyWindow : QDialog, Localizer
|
struct PrettyWindow : QDialog
|
||||||
{
|
{
|
||||||
PrettyWindow(const char* name)
|
PrettyWindow(const char* name)
|
||||||
{
|
{
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
ui.display->setGraphicsEffect(outliner = new Outliner);
|
ui.display->setGraphicsEffect(&outliner);
|
||||||
setWindowFlags(Qt::FramelessWindowHint);
|
setWindowFlags(Qt::FramelessWindowHint);
|
||||||
setAttribute(Qt::WA_TranslucentBackground);
|
setAttribute(Qt::WA_TranslucentBackground);
|
||||||
|
|
||||||
settings.beginGroup(name);
|
settings.beginGroup(name);
|
||||||
QFont font = ui.display->font();
|
QFont font = ui.display->font();
|
||||||
if (font.fromString(settings.value(FONT, font.toString()).toString())) ui.display->setFont(font);
|
if (font.fromString(settings.value(FONT, font.toString()).toString())) ui.display->setFont(font);
|
||||||
SetBackgroundColor(settings.value(BG_COLOR, backgroundColor).value<QColor>());
|
setBgColor(settings.value(BG_COLOR, bgColor).value<QColor>());
|
||||||
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] { setBgColor(colorPrompt(this, bgColor, 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,33 +73,8 @@ 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 };
|
QSettings settings{ CONFIG_FILE, QSettings::IniFormat, this };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RequestFont()
|
void RequestFont()
|
||||||
@ -124,48 +86,47 @@ private:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void SetBackgroundColor(QColor color)
|
void setBgColor(QColor color)
|
||||||
{
|
{
|
||||||
if (!color.isValid()) return;
|
if (!color.isValid()) return;
|
||||||
if (color.alpha() == 0) color.setAlpha(1);
|
if (color.alpha() == 0) color.setAlpha(1);
|
||||||
backgroundColor = color;
|
bgColor = color;
|
||||||
repaint();
|
repaint();
|
||||||
settings.setValue(BG_COLOR, color.name(QColor::HexArgb));
|
settings.setValue(BG_COLOR, color.name(QColor::HexArgb));
|
||||||
};
|
};
|
||||||
|
|
||||||
QColor TextColor()
|
QColor textColor()
|
||||||
{
|
{
|
||||||
return ui.display->palette().color(QPalette::WindowText);
|
return ui.display->palette().color(QPalette::WindowText);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetTextColor(QColor color)
|
void setTextColor(QColor color)
|
||||||
{
|
{
|
||||||
if (!color.isValid()) return;
|
if (!color.isValid()) return;
|
||||||
ui.display->setPalette(QPalette(color, {}, {}, {}, {}, {}, {}));
|
ui.display->setPalette(QPalette(color, {}, {}, {}, {}, {}, {}));
|
||||||
settings.setValue(TEXT_COLOR, color.name(QColor::HexArgb));
|
settings.setValue(TEXT_COLOR, color.name(QColor::HexArgb));
|
||||||
};
|
};
|
||||||
|
|
||||||
void SetOutline(bool enable)
|
void setOutline(bool enable)
|
||||||
{
|
{
|
||||||
if (enable)
|
if (enable)
|
||||||
{
|
{
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void paintEvent(QPaintEvent*) override
|
void paintEvent(QPaintEvent*) override
|
||||||
{
|
{
|
||||||
QPainter(this).fillRect(rect(), backgroundColor);
|
QPainter(this).fillRect(rect(), bgColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool autoHide = false, hidden = false;
|
QColor bgColor{ palette().window().color() };
|
||||||
QColor backgroundColor{ palette().window().color() };
|
struct : QGraphicsEffect
|
||||||
struct Outliner : QGraphicsEffect
|
|
||||||
{
|
{
|
||||||
void draw(QPainter* painter) override
|
void draw(QPainter* painter) override
|
||||||
{
|
{
|
||||||
@ -184,27 +145,24 @@ 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());
|
setGeometry(settings.value(WINDOW, geometry()).toRect());
|
||||||
|
|
||||||
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 },
|
{ SHOW_ORIGINAL, true, &ExtraWindow::setShowOriginal },
|
||||||
{ CENTERED_TEXT, false, &ExtraWindow::SetCenteredText },
|
{ DICTIONARY, false, &ExtraWindow::setUseDictionary },
|
||||||
{ AUTO_RESIZE_WINDOW_HEIGHT, false, &ExtraWindow::SetAutoResize },
|
|
||||||
{ SHOW_ORIGINAL, true, &ExtraWindow::SetShowOriginal },
|
|
||||||
{ ORIGINAL_AFTER_TRANSLATION, true, &ExtraWindow::SetShowOriginalAfterTranslation },
|
|
||||||
{ DICTIONARY, false, &ExtraWindow::SetUseDictionary },
|
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
// delay processing anything until Textractor has finished initializing
|
// delay processing anything until Textractor has finished initializing
|
||||||
@ -213,15 +171,10 @@ public:
|
|||||||
action->setCheckable(true);
|
action->setCheckable(true);
|
||||||
action->setChecked(default);
|
action->setChecked(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.addAction(CLICK_THROUGH, this, &ExtraWindow::ToggleClickThrough);
|
|
||||||
|
|
||||||
ui.display->installEventFilter(this);
|
ui.display->installEventFilter(this);
|
||||||
qApp->installNativeEventFilter(this);
|
|
||||||
|
|
||||||
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,90 +187,33 @@ public:
|
|||||||
|
|
||||||
void AddSentence(QString sentence)
|
void AddSentence(QString sentence)
|
||||||
{
|
{
|
||||||
|
if (!showOriginal) sentence = sentence.section('\n', sentence.count('\n') / 2 + 1);
|
||||||
sanitize(sentence);
|
sanitize(sentence);
|
||||||
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()
|
void setTopmost(bool topmost)
|
||||||
{
|
{
|
||||||
if (sentenceHistory.empty()) return;
|
SetWindowPos((HWND)winId(), topmost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||||
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)
|
|
||||||
{
|
|
||||||
for (auto window : { winId(), dictionaryWindow.winId() })
|
|
||||||
SetWindowPos((HWND)window, topmost ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
|
||||||
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)
|
void setShowOriginal(bool showOriginal)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
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)
|
void setUseDictionary(bool useDictionary)
|
||||||
{
|
|
||||||
settings.setValue(ORIGINAL_AFTER_TRANSLATION, this->showOriginalAfterTranslation = showOriginalAfterTranslation);
|
|
||||||
DisplaySentence();
|
|
||||||
};
|
|
||||||
|
|
||||||
void SetUseDictionary(bool useDictionary)
|
|
||||||
{
|
{
|
||||||
if (useDictionary)
|
if (useDictionary)
|
||||||
{
|
{
|
||||||
@ -331,77 +227,18 @@ private:
|
|||||||
settings.setValue(DICTIONARY, this->useDictionary = useDictionary);
|
settings.setValue(DICTIONARY, this->useDictionary = useDictionary);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ToggleClickThrough()
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
const QFont& font = ui.display->font();
|
|
||||||
if (cachedDisplayInfo.CompareExchange(ui.display))
|
|
||||||
{
|
|
||||||
QFontMetrics fontMetrics(font, ui.display);
|
|
||||||
int flags = Qt::TextWordWrap | (ui.display->alignment() & (Qt::AlignLeft | Qt::AlignHCenter));
|
|
||||||
textPositionMap.clear();
|
|
||||||
for (int i = 0, height = 0, lineBreak = 0; i < sentence.size(); ++i)
|
|
||||||
{
|
|
||||||
int block = 1;
|
|
||||||
for (int charHeight = fontMetrics.boundingRect(0, 0, 1, INT_MAX, flags, 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);
|
|
||||||
auto boundingRect = fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, flags, sentence.left(i + block));
|
|
||||||
if (boundingRect.height() > height)
|
|
||||||
{
|
|
||||||
height = boundingRect.height();
|
|
||||||
lineBreak = i;
|
|
||||||
}
|
|
||||||
textPositionMap.push_back({
|
|
||||||
fontMetrics.boundingRect(0, 0, ui.display->width(), INT_MAX, flags, sentence.mid(lineBreak, i - lineBreak + 1)).right() + 1,
|
|
||||||
height
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int i;
|
|
||||||
for (i = 0; i < textPositionMap.size(); ++i) if (textPositionMap[i].y() > mouse.y() && textPositionMap[i].x() > mouse.x()) break;
|
|
||||||
if (i == textPositionMap.size() || (mouse - textPositionMap[i]).manhattanLength() > font.pointSize() * 3) return dictionaryWindow.hide();
|
|
||||||
if (sentence.mid(i) == dictionaryWindow.term) return dictionaryWindow.ShowDefinition();
|
|
||||||
dictionaryWindow.ui.display->setFixedWidth(ui.display->width() * 3 / 4);
|
|
||||||
dictionaryWindow.SetTerm(sentence.mid(i));
|
|
||||||
int left = i == 0 ? 0 : textPositionMap[i - 1].x(), right = textPositionMap[i].x(),
|
|
||||||
x = textPositionMap[i].x() > ui.display->width() / 2 ? -dictionaryWindow.width() + (right * 3 + left) / 4 : (left * 3 + right) / 4, y = 0;
|
|
||||||
for (auto point : textPositionMap) if (point.y() > y && point.y() < textPositionMap[i].y()) y = point.y();
|
|
||||||
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::MouseButtonRelease && ui.display->hasSelectedText())
|
||||||
|
{
|
||||||
|
dictionaryWindow.ui.display->setFixedWidth(ui.display->width());
|
||||||
|
dictionaryWindow.setTerm(ui.display->text().mid(ui.display->selectionStart()));
|
||||||
|
dictionaryWindow.move({ x(), y() - dictionaryWindow.height() });
|
||||||
|
}
|
||||||
|
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,49 +247,27 @@ 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;
|
||||||
QPoint oldPos;
|
QPoint oldPos;
|
||||||
|
|
||||||
class
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
bool CompareExchange(QLabel* display)
|
|
||||||
{
|
|
||||||
if (display->text() == text && display->font() == font && display->width() == width && display->alignment() == alignment) return false;
|
|
||||||
text = display->text();
|
|
||||||
font = display->font();
|
|
||||||
width = display->width();
|
|
||||||
alignment = display->alignment();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
QString text;
|
|
||||||
QFont font;
|
|
||||||
int width;
|
|
||||||
Qt::Alignment alignment;
|
|
||||||
} cachedDisplayInfo;
|
|
||||||
std::vector<QPoint> textPositionMap;
|
|
||||||
|
|
||||||
std::vector<QString> sentenceHistory;
|
std::vector<QString> sentenceHistory;
|
||||||
int historyIndex = 0;
|
int historyIndex = 0;
|
||||||
|
|
||||||
class DictionaryWindow : public PrettyWindow
|
class DictionaryWindow : public PrettyWindow
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
DictionaryWindow() : PrettyWindow("Dictionary Window")
|
DictionaryWindow() :
|
||||||
|
PrettyWindow("Dictionary Window")
|
||||||
{
|
{
|
||||||
ui.display->setSizePolicy({ QSizePolicy::Fixed, QSizePolicy::Minimum });
|
ui.display->setSizePolicy({ QSizePolicy::Fixed, QSizePolicy::Minimum });
|
||||||
}
|
}
|
||||||
@ -467,72 +282,45 @@ private:
|
|||||||
catch (std::filesystem::filesystem_error) { return; }
|
catch (std::filesystem::filesystem_error) { return; }
|
||||||
|
|
||||||
dictionary.clear();
|
dictionary.clear();
|
||||||
charStorage.clear();
|
definitionStorage.clear();
|
||||||
|
|
||||||
auto StoreCopy = [&](std::string_view string)
|
auto StoreCopy = [&](const std::string& string)
|
||||||
{
|
{
|
||||||
auto location = &*charStorage.insert(charStorage.end(), string.begin(), string.end());
|
return &*definitionStorage.insert(definitionStorage.end(), string.c_str(), string.c_str() + string.size() + 1);
|
||||||
charStorage.push_back(0);
|
|
||||||
return location;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
charStorage.reserve(std::filesystem::file_size(DICTIONARY_SAVE_FILE));
|
std::string savedDictionary(std::istreambuf_iterator(std::ifstream(DICTIONARY_SAVE_FILE)), {});
|
||||||
std::ifstream stream(DICTIONARY_SAVE_FILE);
|
definitionStorage.reserve(savedDictionary.size());
|
||||||
BlockMarkupIterator savedDictionary(stream, Array<std::string_view>{ "|TERM|", "|DEFINITION|" });
|
for (size_t end = 0; ;)
|
||||||
while (auto read = savedDictionary.Next())
|
|
||||||
{
|
{
|
||||||
const auto& [terms, definition] = read.value();
|
size_t term = savedDictionary.find("|TERM|", end);
|
||||||
auto storedDefinition = StoreCopy(definition);
|
size_t definition = savedDictionary.find("|DEFINITION|", term);
|
||||||
std::string_view termsView = terms;
|
if ((end = savedDictionary.find("|END|", definition)) == std::string::npos) break;
|
||||||
size_t start = 0, end = termsView.find("|TERM|");
|
auto storedDefinition = StoreCopy(savedDictionary.substr(definition + 12, end - definition - 12));
|
||||||
while (end != std::string::npos)
|
for (size_t next; (next = savedDictionary.find("|TERM|", term + 1)) != std::string::npos && next < definition; term = next)
|
||||||
{
|
dictionary.push_back({ StoreCopy(savedDictionary.substr(term + 6, next - term - 6)), storedDefinition });
|
||||||
dictionary.push_back(DictionaryEntry{ StoreCopy(termsView.substr(start, end - start)), storedDefinition });
|
dictionary.push_back({ StoreCopy(savedDictionary.substr(term + 6, definition - term - 6)), storedDefinition });
|
||||||
start = end + 6;
|
|
||||||
end = termsView.find("|TERM|", start);
|
|
||||||
}
|
|
||||||
dictionary.push_back(DictionaryEntry{ StoreCopy(termsView.substr(start)), storedDefinition });
|
|
||||||
}
|
|
||||||
std::stable_sort(dictionary.begin(), dictionary.end());
|
|
||||||
|
|
||||||
inflections.clear();
|
|
||||||
stream.seekg(0);
|
|
||||||
BlockMarkupIterator savedInflections(stream, Array<std::string_view>{ "|ROOT|", "|INFLECTS TO|", "|NAME|" });
|
|
||||||
while (auto read = savedInflections.Next())
|
|
||||||
{
|
|
||||||
const auto& [root, inflectsTo, name] = read.value();
|
|
||||||
if (!inflections.emplace_back(Inflection{
|
|
||||||
S(root),
|
|
||||||
QRegularExpression(QRegularExpression::anchoredPattern(S(inflectsTo)), QRegularExpression::UseUnicodePropertiesOption),
|
|
||||||
S(name)
|
|
||||||
}).inflectsTo.isValid()) TEXTRACTOR_MESSAGE(L"Invalid regex: %s", StringToWideString(inflectsTo));
|
|
||||||
}
|
}
|
||||||
|
std::sort(dictionary.begin(), dictionary.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetTerm(QString term)
|
void setTerm(QString term)
|
||||||
{
|
{
|
||||||
this->term = term;
|
|
||||||
UpdateDictionary();
|
UpdateDictionary();
|
||||||
definitions.clear();
|
definitions.clear();
|
||||||
definitionIndex = 0;
|
definitionIndex = 0;
|
||||||
std::unordered_set<const char*> foundDefinitions;
|
std::unordered_set<std::string_view> definitionSet;
|
||||||
for (term = term.left(100); !term.isEmpty(); term.chop(1))
|
for (QByteArray utf8term = term.left(200).toUtf8(); !utf8term.isEmpty(); utf8term.chop(1))
|
||||||
for (const auto& [rootTerm, definition, inflections] : LookupDefinitions(term, foundDefinitions))
|
for (auto [it, end] = std::equal_range(dictionary.begin(), dictionary.end(), DictionaryEntry{ utf8term }); it != end; ++it)
|
||||||
definitions.push_back(
|
if (definitionSet.emplace(it->definition).second)
|
||||||
QStringLiteral("<h3>%1 (%5/%6)</h3><small>%2%3</small>%4").arg(
|
definitions.push_back(QStringLiteral("<h3>%1 (%3/%4)</h3>%2").arg(utf8term, it->definition));
|
||||||
term.split("<<")[0].toHtmlEscaped(),
|
|
||||||
rootTerm.split("<<")[0].toHtmlEscaped(),
|
|
||||||
inflections.join(""),
|
|
||||||
definition
|
|
||||||
)
|
|
||||||
);
|
|
||||||
for (int i = 0; i < definitions.size(); ++i) definitions[i] = definitions[i].arg(i + 1).arg(definitions.size());
|
for (int i = 0; i < definitions.size(); ++i) definitions[i] = definitions[i].arg(i + 1).arg(definitions.size());
|
||||||
ShowDefinition();
|
ShowDefinition();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowDefinition()
|
void ShowDefinition()
|
||||||
{
|
{
|
||||||
if (definitions.empty()) return hide();
|
if (definitions.empty()) return;
|
||||||
ui.display->setText(definitions[definitionIndex]);
|
ui.display->setText(definitions[definitionIndex]);
|
||||||
adjustSize();
|
adjustSize();
|
||||||
resize(width(), 1);
|
resize(width(), 1);
|
||||||
@ -546,31 +334,8 @@ private:
|
|||||||
bool operator<(DictionaryEntry other) const { return strcmp(term, other.term) < 0; }
|
bool operator<(DictionaryEntry other) const { return strcmp(term, other.term) < 0; }
|
||||||
};
|
};
|
||||||
std::vector<DictionaryEntry> dictionary;
|
std::vector<DictionaryEntry> dictionary;
|
||||||
QString term;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct LookupResult
|
|
||||||
{
|
|
||||||
QString term;
|
|
||||||
QString definition;
|
|
||||||
QStringList inflectionsUsed;
|
|
||||||
};
|
|
||||||
std::vector<LookupResult> LookupDefinitions(QString term, std::unordered_set<const char*>& foundDefinitions, QStringList inflectionsUsed = {})
|
|
||||||
{
|
|
||||||
std::vector<LookupResult> results;
|
|
||||||
for (auto [it, end] = std::equal_range(dictionary.begin(), dictionary.end(), DictionaryEntry{ term.toUtf8() }); it != end; ++it)
|
|
||||||
if (foundDefinitions.emplace(it->definition).second) results.push_back({ term, it->definition, inflectionsUsed });
|
|
||||||
for (const auto& inflection : inflections) if (auto match = inflection.inflectsTo.match(term); match.hasMatch())
|
|
||||||
{
|
|
||||||
QStringList currentInflectionsUsed = inflectionsUsed;
|
|
||||||
currentInflectionsUsed.push_front(inflection.name);
|
|
||||||
QString root;
|
|
||||||
for (const auto& ch : inflection.root) root += ch.isDigit() ? match.captured(ch.digitValue()) : ch;
|
|
||||||
for (const auto& definition : LookupDefinitions(root, foundDefinitions, currentInflectionsUsed)) results.push_back(definition);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wheelEvent(QWheelEvent* event) override
|
void wheelEvent(QWheelEvent* event) override
|
||||||
{
|
{
|
||||||
int scroll = event->angleDelta().y();
|
int scroll = event->angleDelta().y();
|
||||||
@ -581,16 +346,8 @@ private:
|
|||||||
move(x(), y() + oldHeight - height());
|
move(x(), y() + oldHeight - height());
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Inflection
|
|
||||||
{
|
|
||||||
QString root;
|
|
||||||
QRegularExpression inflectsTo;
|
|
||||||
QString name;
|
|
||||||
};
|
|
||||||
std::vector<Inflection> inflections;
|
|
||||||
|
|
||||||
std::filesystem::file_time_type dictionaryFileLastWrite;
|
std::filesystem::file_time_type dictionaryFileLastWrite;
|
||||||
std::vector<char> charStorage;
|
std::vector<char> definitionStorage;
|
||||||
std::vector<QString> definitions;
|
std::vector<QString> definitions;
|
||||||
int definitionIndex;
|
int definitionIndex;
|
||||||
} dictionaryWindow;
|
} dictionaryWindow;
|
||||||
|
@ -6,13 +6,10 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>800</width>
|
<width>400</width>
|
||||||
<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">
|
||||||
@ -30,9 +27,6 @@
|
|||||||
<property name="contextMenuPolicy">
|
<property name="contextMenuPolicy">
|
||||||
<enum>Qt::CustomContextMenu</enum>
|
<enum>Qt::CustomContextMenu</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="lineWidth">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
<property name="alignment">
|
||||||
<set>Qt::AlignTop</set>
|
<set>Qt::AlignTop</set>
|
||||||
</property>
|
</property>
|
||||||
|
@ -1,267 +1,127 @@
|
|||||||
#include "qtcommon.h"
|
#include "extension.h"
|
||||||
#include "translatewrapper.h"
|
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include <ctime>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
extern const wchar_t* TRANSLATION_ERROR;
|
extern const wchar_t* TRANSLATION_ERROR;
|
||||||
|
|
||||||
const char* TRANSLATION_PROVIDER = "Google Translate";
|
extern Synchronized<std::wstring> translateTo;
|
||||||
const char* GET_API_KEY_FROM = "https://console.cloud.google.com/marketplace/product/google/translate.googleapis.com";
|
|
||||||
extern const QStringList languagesTo
|
const char* TRANSLATION_PROVIDER = "Google";
|
||||||
|
QStringList languages
|
||||||
{
|
{
|
||||||
"Afrikaans",
|
"English: en",
|
||||||
"Albanian",
|
"Afrikaans: af",
|
||||||
"Amharic",
|
"Arabic: ar",
|
||||||
"Arabic",
|
"Albanian: sq",
|
||||||
"Armenian",
|
"Belarusian: be",
|
||||||
"Azerbaijani",
|
"Bengali: bn",
|
||||||
"Basque",
|
"Bosnian: bs",
|
||||||
"Belarusian",
|
"Bulgarian: bg",
|
||||||
"Bengali",
|
"Catalan: ca",
|
||||||
"Bosnian",
|
"Chinese(Simplified): zh-CH",
|
||||||
"Bulgarian",
|
"Chinese(Traditional): zh-TW",
|
||||||
"Catalan",
|
"Croatian: hr",
|
||||||
"Cebuano",
|
"Czech: cs",
|
||||||
"Chichewa",
|
"Danish: da",
|
||||||
"Chinese (Simplified)",
|
"Dutch: nl",
|
||||||
"Chinese (Traditional)",
|
"Esperanto: eo",
|
||||||
"Corsican",
|
"Estonian: et",
|
||||||
"Croatian",
|
"Filipino: tl",
|
||||||
"Czech",
|
"Finnish: fi",
|
||||||
"Danish",
|
"French: fr",
|
||||||
"Dutch",
|
"Galician: gl",
|
||||||
"English",
|
"German: de",
|
||||||
"Esperanto",
|
"Greek: el",
|
||||||
"Estonian",
|
"Hebrew: iw",
|
||||||
"Filipino",
|
"Hindi: hi",
|
||||||
"Finnish",
|
"Hungarian: hu",
|
||||||
"French",
|
"Icelandic: is",
|
||||||
"Frisian",
|
"Indonesian: id",
|
||||||
"Galician",
|
"Irish: ga",
|
||||||
"Georgian",
|
"Italian: it",
|
||||||
"German",
|
"Japanese: ja",
|
||||||
"Greek",
|
"Klingon: tlh",
|
||||||
"Gujarati",
|
"Korean: ko",
|
||||||
"Haitian Creole",
|
"Latin: la",
|
||||||
"Hausa",
|
"Latvian: lv",
|
||||||
"Hawaiian",
|
"Lithuanian: lt",
|
||||||
"Hebrew",
|
"Macedonian: mk",
|
||||||
"Hindi",
|
"Malay: ms",
|
||||||
"Hmong",
|
"Maltese: mt",
|
||||||
"Hungarian",
|
"Norwegian: no",
|
||||||
"Icelandic",
|
"Persian: fa",
|
||||||
"Igbo",
|
"Polish: pl",
|
||||||
"Indonesian",
|
"Portuguese: pt",
|
||||||
"Irish",
|
"Romanian: ro",
|
||||||
"Italian",
|
"Russian: ru",
|
||||||
"Japanese",
|
"Serbian: sr",
|
||||||
"Javanese",
|
"Slovak: sk",
|
||||||
"Kannada",
|
"Slovenian: sl",
|
||||||
"Kazakh",
|
"Somali: so",
|
||||||
"Khmer",
|
"Spanish: es",
|
||||||
"Kinyarwanda",
|
"Swahili: sw",
|
||||||
"Korean",
|
"Swedish: sv",
|
||||||
"Kurdish (Kurmanji)",
|
"Thai: th",
|
||||||
"Kyrgyz",
|
"Turkish: tr",
|
||||||
"Lao",
|
"Ukranian: uk",
|
||||||
"Latin",
|
"Urdu: ur",
|
||||||
"Latvian",
|
"Vietnamese: vi",
|
||||||
"Lithuanian",
|
"Welsh: cy",
|
||||||
"Luxembourgish",
|
"Yiddish: yi",
|
||||||
"Macedonian",
|
"Zulu: zu"
|
||||||
"Malagasy",
|
|
||||||
"Malay",
|
|
||||||
"Malayalam",
|
|
||||||
"Maltese",
|
|
||||||
"Maori",
|
|
||||||
"Marathi",
|
|
||||||
"Mongolian",
|
|
||||||
"Myanmar (Burmese)",
|
|
||||||
"Nepali",
|
|
||||||
"Norwegian",
|
|
||||||
"Odia (Oriya)",
|
|
||||||
"Pashto",
|
|
||||||
"Persian",
|
|
||||||
"Polish",
|
|
||||||
"Portuguese",
|
|
||||||
"Punjabi",
|
|
||||||
"Romanian",
|
|
||||||
"Russian",
|
|
||||||
"Samoan",
|
|
||||||
"Scots Gaelic",
|
|
||||||
"Serbian",
|
|
||||||
"Sesotho",
|
|
||||||
"Shona",
|
|
||||||
"Sindhi",
|
|
||||||
"Sinhala",
|
|
||||||
"Slovak",
|
|
||||||
"Slovenian",
|
|
||||||
"Somali",
|
|
||||||
"Spanish",
|
|
||||||
"Sundanese",
|
|
||||||
"Swahili",
|
|
||||||
"Swedish",
|
|
||||||
"Tajik",
|
|
||||||
"Tamil",
|
|
||||||
"Tatar",
|
|
||||||
"Telugu",
|
|
||||||
"Thai",
|
|
||||||
"Turkish",
|
|
||||||
"Turkmen",
|
|
||||||
"Ukrainian",
|
|
||||||
"Urdu",
|
|
||||||
"Uyghur",
|
|
||||||
"Uzbek",
|
|
||||||
"Vietnamese",
|
|
||||||
"Welsh",
|
|
||||||
"Xhosa",
|
|
||||||
"Yiddish",
|
|
||||||
"Yoruba",
|
|
||||||
"Zulu",
|
|
||||||
}, languagesFrom = languagesTo;
|
|
||||||
extern const std::unordered_map<std::wstring, std::wstring> codes
|
|
||||||
{
|
|
||||||
{ { L"Afrikaans" }, { L"af" } },
|
|
||||||
{ { L"Albanian" }, { L"sq" } },
|
|
||||||
{ { L"Amharic" }, { L"am" } },
|
|
||||||
{ { L"Arabic" }, { L"ar" } },
|
|
||||||
{ { L"Armenian" }, { L"hy" } },
|
|
||||||
{ { L"Azerbaijani" }, { L"az" } },
|
|
||||||
{ { L"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" } }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool translateSelectedOnly = false, useRateLimiter = true, rateLimitSelected = false, useCache = true, useFilter = true;
|
unsigned TKK = 0;
|
||||||
int tokenCount = 30, rateLimitTimespan = 60000, maxSentenceSize = 1000;
|
|
||||||
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp)
|
std::wstring GetTranslationUri(const std::wstring& text)
|
||||||
{
|
{
|
||||||
if (!tlp.authKey.empty())
|
// If no TKK available, use this uri. Can't use too much or google will detect unauthorized access
|
||||||
{
|
if (!TKK) return FormatString(L"/translate_a/single?client=gtx&dt=ld&dt=rm&dt=t&tl=%s&q=%s", translateTo->c_str(), text);
|
||||||
std::wstring translateFromComponent = tlp.translateFrom == L"?" ? L"" : L"&source=" + codes.at(tlp.translateFrom);
|
|
||||||
if (HttpRequest httpRequest{
|
|
||||||
L"Mozilla/5.0 Textractor",
|
|
||||||
L"translation.googleapis.com",
|
|
||||||
L"POST",
|
|
||||||
FormatString(L"/language/translate/v2?format=text&target=%s&key=%s%s", codes.at(tlp.translateTo), tlp.authKey, translateFromComponent).c_str(),
|
|
||||||
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() };
|
|
||||||
else return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (HttpRequest httpRequest{
|
// Artikash 8/19/2018: reverse engineered from translate.google.com
|
||||||
L"Mozilla/5.0 Textractor",
|
std::wstring escapedText;
|
||||||
L"translate.google.com",
|
unsigned a = time(NULL) / 3600, b = a; // the first part of TKK
|
||||||
L"GET",
|
for (unsigned char ch : WideStringToString(text))
|
||||||
FormatString(L"/m?sl=%s&tl=%s&q=%s", codes.at(tlp.translateFrom), codes.at(tlp.translateTo), Escape(text)).c_str()
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
auto start = httpRequest.response.find(L"result-container\">"), end = httpRequest.response.find(L'<', start);
|
escapedText += FormatString(L"%%%02X", (int)ch);
|
||||||
if (end != std::string::npos) return { true, HTML::Unescape(httpRequest.response.substr(start + 18, end - start - 18)) };
|
a += ch;
|
||||||
return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
a += a << 10;
|
||||||
|
a ^= a >> 6;
|
||||||
|
}
|
||||||
|
a += a << 3;
|
||||||
|
a ^= a >> 11;
|
||||||
|
a += a << 15;
|
||||||
|
a ^= TKK;
|
||||||
|
a %= 1000000;
|
||||||
|
|
||||||
|
return FormatString(L"/translate_a/single?client=webapp&dt=ld&dt=rm&dt=t&sl=auto&tl=%s&tk=%u.%u&q=%s", translateTo->c_str(), a, a ^ b, escapedText);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsHash(const std::wstring& result)
|
||||||
|
{
|
||||||
|
return result.size() == 32 && std::all_of(result.begin(), result.end(), [](char ch) { return (ch >= L'0' && ch <= L'9') || (ch >= L'a' && ch <= L'z'); });
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||||
|
{
|
||||||
|
if (!TKK)
|
||||||
|
if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translate.google.com", L"GET", L"/" })
|
||||||
|
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"(\\d{7,})'")))
|
||||||
|
_InterlockedCompareExchange(&TKK, stoll(results[1]), 0);
|
||||||
|
|
||||||
|
if (HttpRequest httpRequest{ L"Mozilla/5.0 Textractor", L"translate.googleapis.com", L"GET", GetTranslationUri(text).c_str() })
|
||||||
|
{
|
||||||
|
// Response formatted as JSON: starts with "[[[" and translation is enclosed in quotes followed by a comma
|
||||||
|
if (httpRequest.response[0] == L'[')
|
||||||
|
{
|
||||||
|
std::wstring translation;
|
||||||
|
for (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"\\[\"(.*?)\",[n\"]")); httpRequest.response = results.suffix())
|
||||||
|
if (!IsHash(results[1])) translation += std::wstring(results[1]) + L" ";
|
||||||
|
if (!translation.empty()) return { true, translation };
|
||||||
|
}
|
||||||
|
return { false, FormatString(L"%s (TKK=%u)", TRANSLATION_ERROR, _InterlockedExchange(&TKK, 0)) };
|
||||||
}
|
}
|
||||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
#include "extension.h"
|
#include "extension.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include <fstream>
|
||||||
#include <QPlainTextEdit>
|
#include <QPlainTextEdit>
|
||||||
|
|
||||||
extern const char* LUA_INTRO;
|
extern const char* LUA_INTRO;
|
||||||
extern const char* LOAD_SCRIPT;
|
extern const char* LOAD_LUA_SCRIPT;
|
||||||
extern const wchar_t* LUA_ERROR;
|
extern const wchar_t* LUA_ERROR;
|
||||||
|
|
||||||
constexpr auto LUA_SAVE_FILE = u8"Textractor.lua";
|
constexpr auto LUA_SAVE_FILE = u8"Textractor.lua";
|
||||||
@ -40,10 +42,10 @@ bool logErrors = true;
|
|||||||
Synchronized<std::string> script;
|
Synchronized<std::string> script;
|
||||||
std::atomic<int> revCount = 0;
|
std::atomic<int> revCount = 0;
|
||||||
|
|
||||||
class Window : public QDialog, Localizer
|
class Window : public QMainWindow
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
Window()
|
||||||
{
|
{
|
||||||
connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript);
|
connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript);
|
||||||
|
|
||||||
@ -52,10 +54,9 @@ public:
|
|||||||
layout.addWidget(&loadButton);
|
layout.addWidget(&loadButton);
|
||||||
|
|
||||||
resize(800, 600);
|
resize(800, 600);
|
||||||
|
setCentralWidget(¢ralWidget);
|
||||||
setWindowTitle("Lua");
|
setWindowTitle("Lua");
|
||||||
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
||||||
|
|
||||||
LoadScript();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~Window()
|
~Window()
|
||||||
@ -76,22 +77,23 @@ private:
|
|||||||
QTextFile(LUA_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate).write(scriptEditor.toPlainText().toUtf8());
|
QTextFile(LUA_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate).write(scriptEditor.toPlainText().toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
QHBoxLayout layout{ this };
|
QWidget centralWidget{ this };
|
||||||
QPlainTextEdit scriptEditor{ QTextFile(LUA_SAVE_FILE, QIODevice::ReadOnly).readAll(), this };
|
QHBoxLayout layout{ ¢ralWidget };
|
||||||
QPushButton loadButton{ LOAD_SCRIPT, this };
|
QPlainTextEdit scriptEditor{ QTextFile(LUA_SAVE_FILE, QIODevice::ReadOnly).readAll(), ¢ralWidget };
|
||||||
|
QPushButton loadButton{ LOAD_LUA_SCRIPT, ¢ralWidget };
|
||||||
} window;
|
} window;
|
||||||
|
|
||||||
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||||
{
|
{
|
||||||
thread_local struct { std::unique_ptr<lua_State, Functor<lua_close>> L{ luaL_newstate() }; operator lua_State*() { return L.get(); } } L;
|
thread_local static struct { std::unique_ptr<lua_State, Functor<lua_close>> L{ luaL_newstate() }; operator lua_State*() { return L.get(); } } L;
|
||||||
thread_local auto _ = (luaL_openlibs(L), luaL_dostring(L, "function ProcessSentence() end"));
|
thread_local static auto _ = (luaL_openlibs(L), luaL_dostring(L, "function ProcessSentence() end"));
|
||||||
thread_local int revCount = 0;
|
thread_local static int revCount = 0;
|
||||||
|
|
||||||
if (::revCount > revCount)
|
if (::revCount > revCount)
|
||||||
{
|
{
|
||||||
revCount = ::revCount;
|
revCount = ::revCount;
|
||||||
luaL_dostring(L, "ProcessSentence = nil");
|
luaL_dostring(L, "ProcessSentence = nil");
|
||||||
if (luaL_dostring(L, script.Copy().c_str()) != LUA_OK)
|
if (luaL_dostring(L, script->c_str()) != LUA_OK)
|
||||||
{
|
{
|
||||||
sentence += L"\n" + FormatString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)));
|
sentence += L"\n" + FormatString(LUA_ERROR, StringToWideString(lua_tolstring(L, 1, nullptr)));
|
||||||
lua_settop(L, 0);
|
lua_settop(L, 0);
|
||||||
|
@ -1,32 +1,28 @@
|
|||||||
#include "network.h"
|
#include "network.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
HttpRequest::HttpRequest(
|
HttpRequest::HttpRequest(
|
||||||
const wchar_t* agentName,
|
const wchar_t* agentName,
|
||||||
const wchar_t* serverName,
|
const wchar_t* serverName,
|
||||||
const wchar_t* action,
|
const wchar_t* action,
|
||||||
const wchar_t* objectName,
|
const wchar_t* objectName,
|
||||||
std::string body,
|
|
||||||
const wchar_t* headers,
|
|
||||||
DWORD port,
|
|
||||||
const wchar_t* referrer,
|
|
||||||
DWORD requestFlags,
|
DWORD requestFlags,
|
||||||
const wchar_t* httpVersion,
|
const wchar_t* httpVersion,
|
||||||
const wchar_t** acceptTypes
|
const wchar_t* referrer,
|
||||||
|
const wchar_t** acceptTypes,
|
||||||
|
const wchar_t* headers,
|
||||||
|
void* body,
|
||||||
|
DWORD bodyLength
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
static std::atomic<HINTERNET> internet = NULL;
|
static std::atomic<HINTERNET> internet = NULL;
|
||||||
if (!internet) internet = WinHttpOpen(agentName, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
|
if (!internet) internet = WinHttpOpen(agentName, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
|
||||||
if (internet)
|
if (internet)
|
||||||
if (InternetHandle connection = WinHttpConnect(internet, serverName, port, 0))
|
if (InternetHandle connection = WinHttpConnect(internet, serverName, INTERNET_DEFAULT_HTTPS_PORT, 0))
|
||||||
if (InternetHandle request = WinHttpOpenRequest(connection, action, objectName, httpVersion, referrer, acceptTypes, requestFlags))
|
if (InternetHandle request = WinHttpOpenRequest(connection, action, objectName, httpVersion, referrer, acceptTypes, requestFlags))
|
||||||
if (WinHttpSendRequest(request, headers, -1UL, body.empty() ? NULL : body.data(), body.size(), body.size(), NULL))
|
if (WinHttpSendRequest(request, headers, -1UL, body, bodyLength, bodyLength, NULL))
|
||||||
{
|
{
|
||||||
WinHttpReceiveResponse(request, NULL);
|
WinHttpReceiveResponse(request, NULL);
|
||||||
|
|
||||||
//DWORD size = 0;
|
|
||||||
//WinHttpQueryHeaders(request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &size, WINHTTP_NO_HEADER_INDEX);
|
|
||||||
//this->headers.resize(size);
|
|
||||||
//WinHttpQueryHeaders(request, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, this->headers.data(), &size, WINHTTP_NO_HEADER_INDEX);
|
|
||||||
std::string data;
|
std::string data;
|
||||||
DWORD availableSize, downloadedSize;
|
DWORD availableSize, downloadedSize;
|
||||||
do
|
do
|
||||||
@ -55,11 +51,17 @@ std::wstring Escape(const std::wstring& text)
|
|||||||
return escaped;
|
return escaped;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Escape(const std::string& text)
|
void Unescape(std::wstring& text)
|
||||||
{
|
{
|
||||||
std::string escaped;
|
for (int i = 0; i < text.size(); ++i)
|
||||||
for (unsigned char ch : text) escaped += FormatString("%%%02X", (int)ch);
|
{
|
||||||
return escaped;
|
if (text[i] == L'\\')
|
||||||
|
{
|
||||||
|
text[i] = 0x200b;
|
||||||
|
if (text[i + 1] == L'r') text[i + 1] = 0x200b; // for some reason \r gets displayed as a newline
|
||||||
|
if (text[i + 1] == L'n') text[i + 1] = L'\n';
|
||||||
|
if (text[i + 1] == L't') text[i + 1] = L'\t';
|
||||||
|
if (text[i + 1] == L'\\') ++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(assert(JSON::Parse<wchar_t>(LR"([{"string":"hello world","boolean":false,"number":1.67e+4,"null":null,"array":[]},"hello world"])")));
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include <winhttp.h>
|
#include <winhttp.h>
|
||||||
#include <variant>
|
|
||||||
|
|
||||||
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
|
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
|
||||||
|
|
||||||
@ -12,222 +12,21 @@ struct HttpRequest
|
|||||||
const wchar_t* serverName,
|
const wchar_t* serverName,
|
||||||
const wchar_t* action,
|
const wchar_t* action,
|
||||||
const wchar_t* objectName,
|
const wchar_t* objectName,
|
||||||
std::string body = "",
|
|
||||||
const wchar_t* headers = NULL,
|
|
||||||
DWORD port = INTERNET_DEFAULT_PORT,
|
|
||||||
const wchar_t* referrer = NULL,
|
|
||||||
DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE,
|
DWORD requestFlags = WINHTTP_FLAG_SECURE | WINHTTP_FLAG_ESCAPE_DISABLE,
|
||||||
const wchar_t* httpVersion = NULL,
|
const wchar_t* httpVersion = NULL,
|
||||||
const wchar_t** acceptTypes = NULL
|
const wchar_t* referrer = NULL,
|
||||||
|
const wchar_t** acceptTypes = NULL,
|
||||||
|
const wchar_t* headers = NULL,
|
||||||
|
void* body = NULL,
|
||||||
|
DWORD bodyLength = 0
|
||||||
);
|
);
|
||||||
operator bool() { return errorCode == ERROR_SUCCESS; }
|
operator bool() { return errorCode == ERROR_SUCCESS; }
|
||||||
|
|
||||||
std::wstring response;
|
std::wstring response;
|
||||||
std::wstring headers;
|
|
||||||
InternetHandle connection = NULL;
|
InternetHandle connection = NULL;
|
||||||
InternetHandle request = NULL;
|
InternetHandle request = NULL;
|
||||||
DWORD errorCode = ERROR_SUCCESS;
|
DWORD errorCode = ERROR_SUCCESS;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::wstring Escape(const std::wstring& text);
|
std::wstring Escape(const std::wstring& text);
|
||||||
std::string Escape(const std::string& text);
|
void Unescape(std::wstring& text);
|
||||||
|
|
||||||
namespace HTML
|
|
||||||
{
|
|
||||||
template <typename C>
|
|
||||||
std::basic_string<C> Unescape(std::basic_string<C> text)
|
|
||||||
{
|
|
||||||
constexpr C
|
|
||||||
lt[] = { '&', 'l', 't', ';' },
|
|
||||||
gt[] = { '&', 'g', 't', ';' },
|
|
||||||
apos1[] = { '&', 'a', 'p', 'o', 's', ';' },
|
|
||||||
apos2[] = { '&', '#', '3', '9', ';' },
|
|
||||||
apos3[] = { '&', '#', 'x', '2', '7', ';' },
|
|
||||||
apos4[] = { '&', '#', 'X', '2', '7', ';' },
|
|
||||||
quot[] = { '&', 'q', 'u', 'o', 't', ';' },
|
|
||||||
amp[] = { '&', 'a', 'm', 'p', ';' };
|
|
||||||
for (int i = 0; i < text.size(); ++i)
|
|
||||||
if (text[i] == '&')
|
|
||||||
for (auto [original, length, replacement] : Array<const C*, size_t, C>{
|
|
||||||
{ lt, std::size(lt), '<' },
|
|
||||||
{ gt, std::size(gt), '>' },
|
|
||||||
{ apos1, std::size(apos1), '\'' },
|
|
||||||
{ apos2, std::size(apos2), '\'' },
|
|
||||||
{ apos3, std::size(apos3), '\'' },
|
|
||||||
{ apos4, std::size(apos4), '\'' },
|
|
||||||
{ quot, std::size(quot), '"' },
|
|
||||||
{ amp, std::size(amp), '&' }
|
|
||||||
}) if (std::char_traits<C>::compare(text.data() + i, original, length) == 0) text.replace(i, length, 1, replacement);
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace JSON
|
|
||||||
{
|
|
||||||
template <typename C>
|
|
||||||
std::basic_string<C> Escape(std::basic_string<C> text)
|
|
||||||
{
|
|
||||||
int oldSize = text.size();
|
|
||||||
text.resize(text.size() + std::count_if(text.begin(), text.end(), [](C ch) { return ch == '\n' || ch == '\r' || ch == '\t' || ch == '\\' || ch == '"'; }));
|
|
||||||
auto out = text.rbegin();
|
|
||||||
for (int i = oldSize - 1; i >= 0; --i)
|
|
||||||
{
|
|
||||||
if (text[i] == '\n') *out++ = 'n';
|
|
||||||
else if (text[i] == '\t') *out++ = 't';
|
|
||||||
else if (text[i] == '\r') *out++ = 'r';
|
|
||||||
else if (text[i] == '\\' || text[i] == '"') *out++ = text[i];
|
|
||||||
else
|
|
||||||
{
|
|
||||||
*out++ = text[i];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
*out++ = '\\';
|
|
||||||
}
|
|
||||||
text.erase(std::remove_if(text.begin(), text.end(), [](uint64_t ch) { return ch < 0x20 || ch == 0x7f; }), text.end());
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename C> struct UTF {};
|
|
||||||
template <> struct UTF<wchar_t>
|
|
||||||
{
|
|
||||||
inline static std::wstring FromCodepoint(unsigned codepoint) { return { (wchar_t)codepoint }; } // TODO: surrogate pairs
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename C>
|
|
||||||
struct Value : private std::variant<std::monostate, std::nullptr_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>
|
|
||||||
{
|
|
||||||
using std::variant<std::monostate, std::nullptr_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>::variant;
|
|
||||||
|
|
||||||
explicit operator bool() const { return index(); }
|
|
||||||
bool IsNull() const { return index() == 1; }
|
|
||||||
auto Boolean() const { return std::get_if<bool>(this); }
|
|
||||||
auto Number() const { return std::get_if<double>(this); }
|
|
||||||
auto String() const { return std::get_if<std::basic_string<C>>(this); }
|
|
||||||
auto Array() const { return std::get_if<std::vector<Value<C>>>(this); }
|
|
||||||
auto Object() const { return std::get_if<std::unordered_map<std::basic_string<C>, Value<C>>>(this); }
|
|
||||||
|
|
||||||
const Value<C>& operator[](std::basic_string<C> key) const
|
|
||||||
{
|
|
||||||
if (auto object = Object()) if (auto it = object->find(key); it != object->end()) return it->second;
|
|
||||||
return failure;
|
|
||||||
}
|
|
||||||
const Value<C>& operator[](int i) const
|
|
||||||
{
|
|
||||||
if (auto array = Array()) if (i < array->size()) return array->at(i);
|
|
||||||
return failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Value<C> failure;
|
|
||||||
};
|
|
||||||
template <typename C> const Value<C> Value<C>::failure;
|
|
||||||
|
|
||||||
template <typename C, int maxDepth = 25>
|
|
||||||
Value<C> Parse(const std::basic_string<C>& text, int64_t& i, int depth)
|
|
||||||
{
|
|
||||||
if (depth > maxDepth) return {};
|
|
||||||
C ch;
|
|
||||||
auto SkipWhitespace = [&]
|
|
||||||
{
|
|
||||||
while (i < text.size() && (text[i] == ' ' || text[i] == '\n' || text[i] == '\r' || text[i] == '\t')) ++i;
|
|
||||||
if (i >= text.size()) return true;
|
|
||||||
ch = text[i];
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
auto ExtractString = [&]
|
|
||||||
{
|
|
||||||
std::basic_string<C> unescaped;
|
|
||||||
i += 1;
|
|
||||||
for (; i < text.size(); ++i)
|
|
||||||
{
|
|
||||||
auto ch = text[i];
|
|
||||||
if (ch == '"') return i += 1, unescaped;
|
|
||||||
if (ch == '\\')
|
|
||||||
{
|
|
||||||
ch = text[i + 1];
|
|
||||||
if (ch == 'u' && isxdigit(text[i + 2]) && isxdigit(text[i + 3]) && isxdigit(text[i + 4]) && isxdigit(text[i + 5]))
|
|
||||||
{
|
|
||||||
char charCode[] = { (char)text[i + 2], (char)text[i + 3], (char)text[i + 4], (char)text[i + 5], 0 };
|
|
||||||
unescaped += UTF<C>::FromCodepoint(strtoul(charCode, nullptr, 16));
|
|
||||||
i += 5;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (auto [original, value] : Array<char, char>{ { 'b', '\b' }, {'f', '\f'}, {'n', '\n'}, {'r', '\r'}, {'t', '\t'} }) if (ch == original)
|
|
||||||
{
|
|
||||||
unescaped.push_back(value);
|
|
||||||
goto replaced;
|
|
||||||
}
|
|
||||||
unescaped.push_back(ch);
|
|
||||||
replaced: i += 1;
|
|
||||||
}
|
|
||||||
else unescaped.push_back(ch);
|
|
||||||
}
|
|
||||||
return unescaped;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (SkipWhitespace()) return {};
|
|
||||||
|
|
||||||
constexpr C nullStr[] = { 'n', 'u', 'l', 'l' }, trueStr[] = { 't', 'r', 'u', 'e' }, falseStr[] = { 'f', 'a', 'l', 's', 'e' };
|
|
||||||
if (ch == nullStr[0])
|
|
||||||
if (std::char_traits<C>::compare(text.data() + i, nullStr, std::size(nullStr)) == 0) return i += std::size(nullStr), nullptr;
|
|
||||||
else return {};
|
|
||||||
if (ch == trueStr[0])
|
|
||||||
if (std::char_traits<C>::compare(text.data() + i, trueStr, std::size(trueStr)) == 0) return i += std::size(trueStr), true;
|
|
||||||
else return {};
|
|
||||||
if (ch == falseStr[0])
|
|
||||||
if (std::char_traits<C>::compare(text.data() + i, falseStr, std::size(falseStr)) == 0) return i += std::size(falseStr), false;
|
|
||||||
else return {};
|
|
||||||
|
|
||||||
if (ch == '-' || (ch >= '0' && ch <= '9'))
|
|
||||||
{
|
|
||||||
std::string number;
|
|
||||||
for (; i < text.size() && ((text[i] >= '0' && text[i] <= '9') || text[i] == '-' || text[i] == '+' || text[i] == 'e' || text[i] == 'E' || text[i] == '.'); ++i)
|
|
||||||
number.push_back(text[i]);
|
|
||||||
return strtod(number.c_str(), NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == '"') return ExtractString();
|
|
||||||
|
|
||||||
if (ch == '[')
|
|
||||||
{
|
|
||||||
std::vector<Value<C>> array;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
i += 1;
|
|
||||||
if (SkipWhitespace()) return {};
|
|
||||||
if (ch == ']') return i += 1, Value<C>(array);
|
|
||||||
if (!array.emplace_back(Parse<C, maxDepth>(text, i, depth + 1))) return {};
|
|
||||||
if (SkipWhitespace()) return {};
|
|
||||||
if (ch == ']') return i += 1, Value<C>(array);
|
|
||||||
if (ch != ',') return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ch == '{')
|
|
||||||
{
|
|
||||||
std::unordered_map<std::basic_string<C>, Value<C>> object;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
i += 1;
|
|
||||||
if (SkipWhitespace()) return {};
|
|
||||||
if (ch == '}') return i += 1, Value<C>(object);
|
|
||||||
if (ch != '"') return {};
|
|
||||||
auto key = ExtractString();
|
|
||||||
if (SkipWhitespace() || ch != ':') return {};
|
|
||||||
i += 1;
|
|
||||||
if (!(object[std::move(key)] = Parse<C, maxDepth>(text, i, depth + 1))) return {};
|
|
||||||
if (SkipWhitespace()) return {};
|
|
||||||
if (ch == '}') return i += 1, Value<C>(object);
|
|
||||||
if (ch != ',') return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename C>
|
|
||||||
Value<C> Parse(const std::basic_string<C>& text)
|
|
||||||
{
|
|
||||||
int64_t start = 0;
|
|
||||||
return Parse(text, start, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,54 +1,34 @@
|
|||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
#include "extension.h"
|
#include "extension.h"
|
||||||
#include "ui_regexfilter.h"
|
#include "ui_regexfilter.h"
|
||||||
#include "module.h"
|
|
||||||
#include "blockmarkup.h"
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
extern const char* REGEX_FILTER;
|
extern const char* REGEX_FILTER;
|
||||||
extern const char* INVALID_REGEX;
|
extern const char* INVALID_REGEX;
|
||||||
extern const char* CURRENT_FILTER;
|
extern const char* CURRENT_FILTER;
|
||||||
|
|
||||||
const char* REGEX_SAVE_FILE = "SavedRegexFilters.txt";
|
std::wregex regex;
|
||||||
|
std::shared_mutex m;
|
||||||
|
|
||||||
std::optional<std::wregex> regex;
|
class Window : public QMainWindow
|
||||||
std::wstring replace = L"$1";
|
|
||||||
concurrency::reader_writer_lock m;
|
|
||||||
DWORD (*GetSelectedProcessId)() = [] { return 0UL; };
|
|
||||||
|
|
||||||
class Window : public QDialog, Localizer
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
Window()
|
||||||
{
|
{
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
|
|
||||||
connect(ui.regexEdit, &QLineEdit::textEdited, this, &Window::SetRegex);
|
connect(ui.input, &QLineEdit::textEdited, this, &Window::setRegex);
|
||||||
connect(ui.saveButton, &QPushButton::clicked, this, &Window::Save);
|
|
||||||
|
|
||||||
setWindowTitle(REGEX_FILTER);
|
setWindowTitle(REGEX_FILTER);
|
||||||
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetRegex(QString regex)
|
|
||||||
{
|
|
||||||
ui.regexEdit->setText(regex);
|
|
||||||
std::scoped_lock lock(m);
|
|
||||||
if (!regex.isEmpty()) try { ::regex = S(regex); }
|
|
||||||
catch (std::regex_error) { return ui.output->setText(INVALID_REGEX); }
|
|
||||||
else ::regex = std::nullopt;
|
|
||||||
ui.output->setText(QString(CURRENT_FILTER).arg(regex));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Save()
|
void setRegex(QString regex)
|
||||||
{
|
{
|
||||||
auto formatted = FormatString(
|
std::lock_guard l(m);
|
||||||
L"\xfeff|PROCESS|%s|FILTER|%s|END|\r\n",
|
try { ::regex = S(regex); }
|
||||||
GetModuleFilename(GetSelectedProcessId()).value_or(FormatString(L"Error getting name of process 0x%X", GetSelectedProcessId())),
|
catch (std::regex_error) { return ui.output->setText(INVALID_REGEX); }
|
||||||
S(ui.regexEdit->text())
|
ui.output->setText(QString(CURRENT_FILTER).arg(regex));
|
||||||
);
|
|
||||||
std::ofstream(REGEX_SAVE_FILE, std::ios::binary | std::ios::app).write((const char*)formatted.c_str(), formatted.size() * sizeof(wchar_t));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ui::FilterWindow ui;
|
Ui::FilterWindow ui;
|
||||||
@ -56,17 +36,8 @@ private:
|
|||||||
|
|
||||||
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||||
{
|
{
|
||||||
static auto _ = GetSelectedProcessId = (DWORD(*)())sentenceInfo["get selected process id"];
|
|
||||||
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"]))
|
std::shared_lock l(m);
|
||||||
{
|
sentence = std::regex_replace(sentence, regex, L"");
|
||||||
std::ifstream stream(REGEX_SAVE_FILE, std::ios::binary);
|
|
||||||
BlockMarkupIterator savedFilters(stream, Array<std::wstring_view>{ L"|PROCESS|", L"|FILTER|" });
|
|
||||||
std::vector<std::wstring> regexes;
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
concurrency::reader_writer_lock::scoped_lock_read readLock(m);
|
|
||||||
if (regex) sentence = std::regex_replace(sentence, regex.value(), replace);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1,60 +1,51 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<ui version="4.0">
|
<ui version="4.0">
|
||||||
<class>FilterWindow</class>
|
<class>FilterWindow</class>
|
||||||
<widget class="QDialog">
|
<widget class="QMainWindow" name="FilterWindow">
|
||||||
<property name="geometry">
|
<property name="geometry">
|
||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>500</width>
|
<width>350</width>
|
||||||
<height>80</height>
|
<height>76</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout">
|
<widget class="QWidget">
|
||||||
<item>
|
<layout class="QVBoxLayout">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<item>
|
||||||
<item>
|
<widget class="QLineEdit" name="input"/>
|
||||||
<widget class="QLineEdit" name="regexEdit"/>
|
</item>
|
||||||
</item>
|
<item>
|
||||||
<item>
|
<widget class="QLabel" name="output">
|
||||||
<widget class="QPushButton" name="saveButton">
|
<property name="text">
|
||||||
<property name="text">
|
<string/>
|
||||||
<string>Save</string>
|
</property>
|
||||||
</property>
|
<property name="alignment">
|
||||||
</widget>
|
<set>Qt::AlignCenter</set>
|
||||||
</item>
|
</property>
|
||||||
</layout>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="output">
|
<widget class="QLabel">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string/>
|
<string><a href="https://regexr.com">regexr.com</a></string>
|
||||||
</property>
|
</property>
|
||||||
<property name="alignment">
|
<property name="textFormat">
|
||||||
<set>Qt::AlignCenter</set>
|
<enum>Qt::RichText</enum>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
<property name="alignment">
|
||||||
</item>
|
<set>Qt::AlignCenter</set>
|
||||||
<item>
|
</property>
|
||||||
<widget class="QLabel">
|
<property name="openExternalLinks">
|
||||||
<property name="text">
|
<bool>true</bool>
|
||||||
<string><a href="https://regexr.com">regexr.com</a></string>
|
</property>
|
||||||
</property>
|
<property name="textInteractionFlags">
|
||||||
<property name="textFormat">
|
<set>Qt::TextBrowserInteraction</set>
|
||||||
<enum>Qt::RichText</enum>
|
</property>
|
||||||
</property>
|
</widget>
|
||||||
<property name="alignment">
|
</item>
|
||||||
<set>Qt::AlignCenter</set>
|
</layout>
|
||||||
</property>
|
</widget>
|
||||||
<property name="openExternalLinks">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="textInteractionFlags">
|
|
||||||
<set>Qt::TextBrowserInteraction</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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 = L'\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 });
|
||||||
|
@ -2,34 +2,34 @@
|
|||||||
|
|
||||||
std::vector<int> GenerateSuffixArray(const std::wstring& text)
|
std::vector<int> GenerateSuffixArray(const std::wstring& text)
|
||||||
{
|
{
|
||||||
std::vector<int> suffixArray(text.size());
|
std::vector<int> identity(text.size());
|
||||||
for (int i = 0; i < text.size(); ++i) suffixArray[i] = i;
|
for (int i = 0; i < text.size(); ++i) identity[i] = i;
|
||||||
|
std::vector<int> suffixArray = identity;
|
||||||
// The below code is a more efficient way of doing this:
|
// The below code is a more efficient way of doing this:
|
||||||
// std::sort(suffixArray.begin(), suffixArray.end(), [&](int a, int b) { return wcscmp(text.c_str() + a, text.c_str() + b) > 0; });
|
// std::sort(suffixArray.begin(), suffixArray.end(), [&](int a, int b) { return wcscmp(text.c_str() + a, text.c_str() + b) > 0; });
|
||||||
std::stable_sort(suffixArray.begin(), suffixArray.end(), [&](int a, int b) { return text[a] > text[b]; });
|
std::stable_sort(suffixArray.begin(), suffixArray.end(), [&](int a, int b) { return text[a] > text[b]; });
|
||||||
std::vector<int> eqClasses(text.begin(), text.end());
|
std::vector<int> classes(text.begin(), text.end());
|
||||||
std::vector<int> count(text.size());
|
|
||||||
for (int length = 1; length < text.size(); length *= 2)
|
for (int length = 1; length < text.size(); length *= 2)
|
||||||
{
|
{
|
||||||
// Determine equivalence class up to length, by checking length / 2 equivalence of suffixes and their following length / 2 suffixes
|
// Determine equivalence class up to length, by checking length / 2 equivalence of suffixes and their following length / 2 suffixes
|
||||||
std::vector<int> prevEqClasses = eqClasses;
|
std::vector<int> oldClasses = classes;
|
||||||
eqClasses[suffixArray[0]] = 0;
|
classes[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];
|
||||||
if (currentSuffix + length < text.size() && prevEqClasses[currentSuffix] == prevEqClasses[lastSuffix] &&
|
int lastSuffix = suffixArray[i - 1];
|
||||||
prevEqClasses[currentSuffix + length / 2] == prevEqClasses[lastSuffix + length / 2]
|
if (currentSuffix + length < text.size() && oldClasses[currentSuffix] == oldClasses[lastSuffix] &&
|
||||||
)
|
oldClasses[currentSuffix + length / 2] == oldClasses.at(lastSuffix + length / 2)) // not completely certain that this will stay in range
|
||||||
eqClasses[currentSuffix] = eqClasses[lastSuffix];
|
classes[currentSuffix] = classes[lastSuffix];
|
||||||
else eqClasses[currentSuffix] = i;
|
else classes[currentSuffix] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort within equivalence class based on order of following suffix after length (orders up to length * 2)
|
// Sort within equivalence class based on order of following suffix after length (orders up to length * 2)
|
||||||
for (int i = 0; i < text.size(); ++i) count[i] = i;
|
std::vector<int> count = identity;
|
||||||
for (auto suffix : std::vector(suffixArray))
|
for (auto suffix : std::vector(suffixArray))
|
||||||
{
|
{
|
||||||
int precedingSuffix = suffix - length;
|
int precedingSuffix = suffix - length;
|
||||||
if (precedingSuffix >= 0) suffixArray[count[eqClasses[precedingSuffix]]++] = precedingSuffix;
|
if (precedingSuffix >= 0) suffixArray[count[classes[precedingSuffix]]++] = precedingSuffix;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (int i = 0; i + 1 < text.size(); ++i)
|
for (int i = 0; i + 1 < text.size(); ++i)
|
||||||
@ -59,12 +59,14 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
{
|
{
|
||||||
std::wstring substring(sentence, suffixArray[i], commonPrefixLength);
|
std::wstring substring(sentence, suffixArray[i], commonPrefixLength);
|
||||||
bool substringCharMap[0x10000] = {};
|
bool substringCharMap[0x10000] = {};
|
||||||
for (auto ch : substring) substringCharMap[ch] = true;
|
for (auto ch : substring)
|
||||||
|
substringCharMap[ch] = true;
|
||||||
|
|
||||||
for (int regionSize = 0, j = 0; j <= sentence.size(); ++j)
|
for (int regionSize = 0, j = 0; j <= sentence.size(); ++j)
|
||||||
if (substringCharMap[sentence[j]]) regionSize += 1;
|
if (substringCharMap[sentence[j]]) regionSize += 1;
|
||||||
else if (regionSize >= commonPrefixLength * 2)
|
else if (regionSize >= commonPrefixLength * 2)
|
||||||
while (regionSize > 0) sentence[j - regionSize--] = ERASED;
|
while (regionSize > 0)
|
||||||
|
sentence[j - regionSize--] = ERASED;
|
||||||
else regionSize = 0;
|
else regionSize = 0;
|
||||||
|
|
||||||
if (!wcsstr(sentence.c_str(), substring.c_str())) std::copy(substring.begin(), substring.end(), sentence.begin() + max(suffixArray[i], suffixArray[i + 1]));
|
if (!wcsstr(sentence.c_str(), substring.c_str())) std::copy(substring.begin(), substring.end(), sentence.begin() + max(suffixArray[i], suffixArray[i + 1]));
|
||||||
|
@ -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);
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
#include "extension.h"
|
#include "extension.h"
|
||||||
#include "blockmarkup.h"
|
#include "trie.h"
|
||||||
|
#include "charstorage.h"
|
||||||
#include <cwctype>
|
#include <cwctype>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <filesystem>
|
||||||
#include <process.h>
|
#include <process.h>
|
||||||
|
|
||||||
extern const wchar_t* REPLACER_INSTRUCTIONS;
|
extern const wchar_t* REPLACER_INSTRUCTIONS;
|
||||||
@ -10,22 +11,16 @@ 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 ReplacementTrie
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Trie(const std::istream& replacementScript)
|
ReplacementTrie(std::vector<std::pair<std::wstring, std::wstring>> replacements)
|
||||||
{
|
{
|
||||||
BlockMarkupIterator replacementScriptParser(replacementScript, Array<std::wstring_view>{ L"|ORIG|", L"|BECOMES|" });
|
for (auto& [original, replacement] : replacements)
|
||||||
while (auto read = replacementScriptParser.Next())
|
if (!original.empty())
|
||||||
{
|
trie.Insert(std::wstring_view(original.c_str(), std::remove_if(original.begin(), original.end(), Ignore) - original.begin()))->SetValue(storage.Store(replacement));
|
||||||
const auto& [original, replacement] = read.value();
|
|
||||||
Node* current = &root;
|
|
||||||
for (auto ch : original) if (!Ignore(ch)) current = Next(current, ch);
|
|
||||||
if (current != &root)
|
|
||||||
current->value = charStorage.insert(charStorage.end(), replacement.c_str(), replacement.c_str() + replacement.size() + 1) - charStorage.begin();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::wstring Replace(const std::wstring& sentence) const
|
std::wstring Replace(const std::wstring& sentence) const
|
||||||
@ -36,17 +31,23 @@ public:
|
|||||||
std::wstring_view replacement(sentence.c_str() + i, 1);
|
std::wstring_view replacement(sentence.c_str() + i, 1);
|
||||||
int originalLength = 1;
|
int originalLength = 1;
|
||||||
|
|
||||||
const Node* current = &root;
|
auto current = trie.Root();
|
||||||
for (int j = i; current && j <= sentence.size(); ++j)
|
for (int j = i; current && j <= sentence.size(); ++j)
|
||||||
{
|
{
|
||||||
if (current->value >= 0)
|
if (const wchar_t* tail = current->Tail())
|
||||||
|
for (; j <= sentence.size() && *tail; ++j)
|
||||||
|
if (Ignore(sentence[j]));
|
||||||
|
else if (sentence[j] == *tail) ++tail;
|
||||||
|
else goto doneSearchingTrie;
|
||||||
|
if (int* value = current->Value())
|
||||||
{
|
{
|
||||||
replacement = charStorage.data() + current->value;
|
replacement = storage.Retrieve(*value);
|
||||||
originalLength = j - i;
|
originalLength = j - i;
|
||||||
}
|
}
|
||||||
if (!Ignore(sentence[j])) current = Next(current, sentence[j]) ? Next(current, sentence[j]) : Next(current, L'^');
|
if (!Ignore(sentence[j])) current = trie.Next(current, sentence[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
doneSearchingTrie:
|
||||||
result += replacement;
|
result += replacement;
|
||||||
i += originalLength;
|
i += originalLength;
|
||||||
}
|
}
|
||||||
@ -55,40 +56,40 @@ public:
|
|||||||
|
|
||||||
bool Empty()
|
bool Empty()
|
||||||
{
|
{
|
||||||
return root.charMap.empty();
|
return trie.Root()->charMap.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool Ignore(wchar_t ch)
|
static bool Ignore(wchar_t ch)
|
||||||
{
|
{
|
||||||
return ch <= 0x20 || iswspace(ch);
|
return ch <= 0x20 || std::iswspace(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Node>
|
CharStorage<wchar_t> storage;
|
||||||
static Node* Next(Node* node, wchar_t ch)
|
Trie<wchar_t, int> trie;
|
||||||
|
} trie = { {} };
|
||||||
|
|
||||||
|
std::vector<std::pair<std::wstring, std::wstring>> Parse(std::wstring_view replacementScript)
|
||||||
|
{
|
||||||
|
std::vector<std::pair<std::wstring, std::wstring>> replacements;
|
||||||
|
for (size_t end = 0; ;)
|
||||||
{
|
{
|
||||||
auto it = std::lower_bound(node->charMap.begin(), node->charMap.end(), ch, [](const auto& one, auto two) { return one.first < two; });
|
size_t original = replacementScript.find(L"|ORIG|", end);
|
||||||
if (it != node->charMap.end() && it->first == ch) return it->second.get();
|
size_t becomes = replacementScript.find(L"|BECOMES|", original);
|
||||||
if constexpr (!std::is_const_v<Node>) return node->charMap.insert(it, { ch, std::make_unique<Node>() })->second.get();
|
if ((end = replacementScript.find(L"|END|", becomes)) == std::wstring::npos) break;
|
||||||
return nullptr;
|
replacements.emplace_back(replacementScript.substr(original + 6, becomes - original - 6), replacementScript.substr(becomes + 9, end - becomes - 9));
|
||||||
}
|
}
|
||||||
|
return replacements;
|
||||||
struct Node
|
}
|
||||||
{
|
|
||||||
std::vector<std::pair<wchar_t, std::unique_ptr<Node>>> charMap;
|
|
||||||
ptrdiff_t value = -1;
|
|
||||||
} root;
|
|
||||||
|
|
||||||
std::vector<wchar_t> charStorage;
|
|
||||||
} trie = { std::istringstream("") };
|
|
||||||
|
|
||||||
void UpdateReplacements()
|
void UpdateReplacements()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (replaceFileLastWrite.exchange(std::filesystem::last_write_time(REPLACE_SAVE_FILE)) == std::filesystem::last_write_time(REPLACE_SAVE_FILE)) return;
|
if (replaceFileLastWrite.exchange(std::filesystem::last_write_time(REPLACE_SAVE_FILE)) == std::filesystem::last_write_time(REPLACE_SAVE_FILE)) return;
|
||||||
std::scoped_lock lock(m);
|
std::vector<BYTE> file(std::istreambuf_iterator(std::ifstream(REPLACE_SAVE_FILE, std::ios::binary)), {});
|
||||||
trie = Trie(std::ifstream(REPLACE_SAVE_FILE, std::ios::binary));
|
std::scoped_lock l(m);
|
||||||
|
trie = ReplacementTrie(Parse({ (wchar_t*)file.data(), file.size() / sizeof(wchar_t) }));
|
||||||
}
|
}
|
||||||
catch (std::filesystem::filesystem_error) { replaceFileLastWrite.store({}); }
|
catch (std::filesystem::filesystem_error) { replaceFileLastWrite.store({}); }
|
||||||
}
|
}
|
||||||
@ -103,9 +104,8 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
|
|||||||
if (trie.Empty())
|
if (trie.Empty())
|
||||||
{
|
{
|
||||||
auto file = std::ofstream(REPLACE_SAVE_FILE, std::ios::binary) << "\xff\xfe";
|
auto file = std::ofstream(REPLACE_SAVE_FILE, std::ios::binary) << "\xff\xfe";
|
||||||
for (auto ch : std::wstring_view(REPLACER_INSTRUCTIONS))
|
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));
|
_spawnlp(_P_DETACH, "notepad", "notepad", REPLACE_SAVE_FILE, NULL); // show file to user
|
||||||
SpawnThread([] { _spawnlp(_P_DETACH, "notepad", "notepad", REPLACE_SAVE_FILE, NULL); }); // show file to user
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -121,22 +121,22 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo)
|
|||||||
{
|
{
|
||||||
UpdateReplacements();
|
UpdateReplacements();
|
||||||
|
|
||||||
concurrency::reader_writer_lock::scoped_lock_read readLock(m);
|
std::shared_lock l(m);
|
||||||
sentence = trie.Replace(sentence);
|
sentence = trie.Replace(sentence);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(
|
TEST(
|
||||||
{
|
{
|
||||||
std::wstring replacementScript = LR"(
|
auto replacements = Parse(LR"(
|
||||||
|ORIG|さよなら|BECOMES|goodbye |END|Ignore this text
|
|ORIG|さよなら|BECOMES|goodbye |END|Ignore this text
|
||||||
And this text ツ
|
And this text ツ
|
||||||
|ORIG|バカ|BECOMES|idiot|END|
|
|ORIG|バカ|BECOMES|idiot|END|
|
||||||
|ORIG|こんにちは |BECOMES| hello|END||ORIG|delet^this|BECOMES||END|)";
|
|ORIG|こんにちは |BECOMES| hello|END||ORIG|delete this|BECOMES||END|)");
|
||||||
Trie replacements(std::istringstream(std::string{ (const char*)replacementScript.c_str(), replacementScript.size() * sizeof(wchar_t) }));
|
assert(replacements.size() == 4);
|
||||||
std::wstring original = LR"(Don't replace this
|
std::wstring original = LR"(Don't replace this
|
||||||
さよなら バカ こんにちは delete this)";
|
さよなら バカ こんにちは delete this)";
|
||||||
std::wstring replaced = Trie(std::move(replacements)).Replace(original);
|
std::wstring replaced = ReplacementTrie(std::move(replacements)).Replace(original);
|
||||||
assert(replaced == L"Don't replace thisgoodbye idiot hello");
|
assert(replaced == L"Don't replace thisgoodbye idiot hello");
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
#include "qtcommon.h"
|
|
||||||
#include "extension.h"
|
|
||||||
#include <QPlainTextEdit>
|
|
||||||
|
|
||||||
extern const char* LOAD_SCRIPT;
|
|
||||||
|
|
||||||
constexpr auto STYLE_SAVE_FILE = u8"Textractor.qss";
|
|
||||||
|
|
||||||
class Window : public QDialog, Localizer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
|
||||||
{
|
|
||||||
connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript);
|
|
||||||
|
|
||||||
if (scriptEditor.toPlainText().isEmpty())
|
|
||||||
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(&loadButton);
|
|
||||||
|
|
||||||
resize(800, 600);
|
|
||||||
setWindowTitle("Styler");
|
|
||||||
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
|
||||||
|
|
||||||
LoadScript();
|
|
||||||
}
|
|
||||||
|
|
||||||
~Window()
|
|
||||||
{
|
|
||||||
qApp->setStyleSheet("");
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void LoadScript()
|
|
||||||
{
|
|
||||||
qApp->setStyleSheet(scriptEditor.toPlainText());
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Save()
|
|
||||||
{
|
|
||||||
QTextFile(STYLE_SAVE_FILE, QIODevice::WriteOnly | QIODevice::Truncate).write(scriptEditor.toPlainText().toUtf8());
|
|
||||||
}
|
|
||||||
|
|
||||||
QHBoxLayout layout{ this };
|
|
||||||
QPlainTextEdit scriptEditor{ QTextFile(STYLE_SAVE_FILE, QIODevice::ReadOnly).readAll(), this };
|
|
||||||
QPushButton loadButton{ LOAD_SCRIPT, this };
|
|
||||||
} window;
|
|
||||||
|
|
||||||
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
@ -1,31 +1,27 @@
|
|||||||
#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 QMainWindow
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
Window()
|
||||||
{
|
{
|
||||||
ui.setupUi(this);
|
connect(&linkButton, &QPushButton::clicked, this, &Window::Link);
|
||||||
ui.linkButton->setText(LINK);
|
|
||||||
ui.unlinkButton->setText(UNLINK);
|
|
||||||
connect(ui.linkButton, &QPushButton::clicked, this, &Window::Link);
|
|
||||||
connect(ui.unlinkButton, &QPushButton::clicked, this, &Window::Unlink);
|
|
||||||
|
|
||||||
|
layout.addWidget(&linkList);
|
||||||
|
layout.addWidget(&linkButton);
|
||||||
|
|
||||||
|
setCentralWidget(¢ralWidget);
|
||||||
setWindowTitle(THREAD_LINKER);
|
setWindowTitle(THREAD_LINKER);
|
||||||
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
@ -34,46 +30,41 @@ 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);
|
std::lock_guard l(m);
|
||||||
if (ok3 && ok4)
|
linkedTextHandles[from].insert(to);
|
||||||
{
|
linkList.addItem(QString::number(from, 16) + "->" + QString::number(to, 16));
|
||||||
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);
|
|
||||||
(link[0] == "All" ? universalLinks : links[link[0].toInt(nullptr, 16)]).erase(link[1].toInt(nullptr, 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::lock_guard l(m);
|
||||||
|
linkedTextHandles[link[0].toInt(nullptr, 16)].erase(link[1].toInt(nullptr, 16));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ui::LinkWindow ui;
|
QWidget centralWidget{ this };
|
||||||
|
QHBoxLayout layout{ ¢ralWidget };
|
||||||
|
QListWidget linkList{ ¢ralWidget };
|
||||||
|
QPushButton linkButton{ LINK, ¢ralWidget };
|
||||||
} 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 l(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(*)(void*, int64_t, const wchar_t*))sentenceInfo["void (*AddSentence)(void* this, int64_t number, const wchar_t* sentence)"])
|
||||||
((void(*)(int64_t, const wchar_t*))action)(link, sentence.c_str());
|
((void*)sentenceInfo["this"], linkedHandle, sentence.c_str());
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
|
@ -1,154 +1,75 @@
|
|||||||
#include "qtcommon.h"
|
#include "qtcommon.h"
|
||||||
#include "extension.h"
|
#include "extension.h"
|
||||||
#include "translatewrapper.h"
|
#include "defs.h"
|
||||||
#include "blockmarkup.h"
|
#include "network.h"
|
||||||
#include <concurrent_priority_queue.h>
|
#include <map>
|
||||||
#include <fstream>
|
#include <QTimer>
|
||||||
#include <QComboBox>
|
|
||||||
|
|
||||||
extern const char* NATIVE_LANGUAGE;
|
extern const char* SELECT_LANGUAGE;
|
||||||
extern const char* TRANSLATE_TO;
|
extern const char* SELECT_LANGUAGE_MESSAGE;
|
||||||
extern const char* TRANSLATE_FROM;
|
extern const char* LANGUAGE_SAVED;
|
||||||
extern const char* TRANSLATE_SELECTED_THREAD_ONLY;
|
|
||||||
extern const char* RATE_LIMIT_ALL_THREADS;
|
|
||||||
extern const char* RATE_LIMIT_SELECTED_THREAD;
|
|
||||||
extern const char* USE_TRANS_CACHE;
|
|
||||||
extern const char* FILTER_GARBAGE;
|
|
||||||
extern const char* MAX_TRANSLATIONS_IN_TIMESPAN;
|
|
||||||
extern const char* TIMESPAN;
|
|
||||||
extern const char* MAX_SENTENCE_SIZE;
|
|
||||||
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 QStringList languages;
|
||||||
extern const QStringList languagesTo, languagesFrom;
|
std::pair<bool, std::wstring> Translate(const std::wstring& text);
|
||||||
extern bool translateSelectedOnly, useRateLimiter, rateLimitSelected, useCache, useFilter;
|
|
||||||
extern int tokenCount, rateLimitTimespan, maxSentenceSize;
|
|
||||||
std::pair<bool, std::wstring> Translate(const std::wstring& text, TranslationParam tlp);
|
|
||||||
|
|
||||||
QFormLayout* display;
|
const char* LANGUAGE = u8"Language";
|
||||||
Settings settings;
|
const QString CACHE_FILE = QString("%1 Cache.txt").arg(TRANSLATION_PROVIDER);
|
||||||
|
|
||||||
namespace
|
Synchronized<std::wstring> translateTo = L"en";
|
||||||
|
QSettings settings(CONFIG_FILE, QSettings::IniFormat);
|
||||||
|
int savedSize;
|
||||||
|
Synchronized<std::map<std::wstring, std::wstring>> translationCache;
|
||||||
|
|
||||||
|
void SaveCache()
|
||||||
{
|
{
|
||||||
Synchronized<TranslationParam> tlp;
|
QTextFile file(CACHE_FILE, QIODevice::WriteOnly | QIODevice::Truncate);
|
||||||
Synchronized<std::unordered_map<std::wstring, std::wstring>> translationCache;
|
auto translationCache = ::translationCache.Acquire();
|
||||||
|
for (const auto& [original, translation] : translationCache.contents)
|
||||||
std::string CacheFile()
|
file.write(S(FormatString(L"%s|T|\n%s|T|\n", original, translation)).toUtf8());
|
||||||
{
|
savedSize = translationCache->size();
|
||||||
return FormatString("%s Cache (%S).txt", TRANSLATION_PROVIDER, tlp->translateTo);
|
|
||||||
}
|
|
||||||
void SaveCache()
|
|
||||||
{
|
|
||||||
std::wstring allTranslations(L"\xfeff");
|
|
||||||
for (const auto& [sentence, translation] : translationCache.Acquire().contents)
|
|
||||||
allTranslations.append(L"|SENTENCE|").append(sentence).append(L"|TRANSLATION|").append(translation).append(L"|END|\r\n");
|
|
||||||
std::ofstream(CacheFile(), std::ios::binary | std::ios::trunc).write((const char*)allTranslations.c_str(), allTranslations.size() * sizeof(wchar_t));
|
|
||||||
}
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Window : public QDialog, Localizer
|
BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
|
||||||
{
|
{
|
||||||
public:
|
switch (ul_reason_for_call)
|
||||||
Window() : QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
{
|
||||||
|
case DLL_PROCESS_ATTACH:
|
||||||
{
|
{
|
||||||
display = new QFormLayout(this);
|
|
||||||
|
|
||||||
settings.beginGroup(TRANSLATION_PROVIDER);
|
settings.beginGroup(TRANSLATION_PROVIDER);
|
||||||
|
if (settings.contains(LANGUAGE)) translateTo->assign(S(settings.value(LANGUAGE).toString()));
|
||||||
|
else QTimer::singleShot(0, []
|
||||||
|
{
|
||||||
|
QString language = QInputDialog::getItem(
|
||||||
|
nullptr,
|
||||||
|
SELECT_LANGUAGE,
|
||||||
|
QString(SELECT_LANGUAGE_MESSAGE).arg(TRANSLATION_PROVIDER),
|
||||||
|
languages,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
nullptr,
|
||||||
|
Qt::WindowCloseButtonHint
|
||||||
|
);
|
||||||
|
translateTo->assign(S(language.split(": ")[1]));
|
||||||
|
settings.setValue(LANGUAGE, S(translateTo->c_str()));
|
||||||
|
QMessageBox::information(nullptr, SELECT_LANGUAGE, QString(LANGUAGE_SAVED).arg(CONFIG_FILE));
|
||||||
|
});
|
||||||
|
|
||||||
auto translateToCombo = new QComboBox(this);
|
QStringList savedCache = QString(QTextFile(CACHE_FILE, QIODevice::ReadOnly).readAll()).split("|T|\n", QString::SkipEmptyParts);
|
||||||
translateToCombo->addItems(languagesTo);
|
for (int i = 0; i < savedCache.size() - 1; i += 2)
|
||||||
int i = -1;
|
translationCache->insert({ S(savedCache[i]), S(savedCache[i + 1]) });
|
||||||
if (settings.contains(TRANSLATE_TO)) i = translateToCombo->findText(settings.value(TRANSLATE_TO).toString());
|
savedSize = translationCache->size();
|
||||||
if (i < 0) i = translateToCombo->findText(NATIVE_LANGUAGE, Qt::MatchStartsWith);
|
|
||||||
if (i < 0) i = translateToCombo->findText("English", Qt::MatchStartsWith);
|
|
||||||
translateToCombo->setCurrentIndex(i);
|
|
||||||
SaveTranslateTo(translateToCombo->currentText());
|
|
||||||
display->addRow(TRANSLATE_TO, translateToCombo);
|
|
||||||
connect(translateToCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateTo);
|
|
||||||
auto translateFromCombo = new QComboBox(this);
|
|
||||||
translateFromCombo->addItem("?");
|
|
||||||
translateFromCombo->addItems(languagesFrom);
|
|
||||||
i = -1;
|
|
||||||
if (settings.contains(TRANSLATE_FROM)) i = translateFromCombo->findText(settings.value(TRANSLATE_FROM).toString());
|
|
||||||
if (i < 0) i = 0;
|
|
||||||
translateFromCombo->setCurrentIndex(i);
|
|
||||||
SaveTranslateFrom(translateFromCombo->currentText());
|
|
||||||
display->addRow(TRANSLATE_FROM, translateFromCombo);
|
|
||||||
connect(translateFromCombo, &QComboBox::currentTextChanged, this, &Window::SaveTranslateFrom);
|
|
||||||
for (auto [value, label] : Array<bool&, const char*>{
|
|
||||||
{ translateSelectedOnly, TRANSLATE_SELECTED_THREAD_ONLY },
|
|
||||||
{ useRateLimiter, RATE_LIMIT_ALL_THREADS },
|
|
||||||
{ rateLimitSelected, RATE_LIMIT_SELECTED_THREAD },
|
|
||||||
{ useCache, USE_TRANS_CACHE },
|
|
||||||
{ useFilter, FILTER_GARBAGE }
|
|
||||||
})
|
|
||||||
{
|
|
||||||
value = settings.value(label, value).toBool();
|
|
||||||
auto checkBox = new QCheckBox(this);
|
|
||||||
checkBox->setChecked(value);
|
|
||||||
display->addRow(label, checkBox);
|
|
||||||
connect(checkBox, &QCheckBox::clicked, [label, &value](bool checked) { settings.setValue(label, value = checked); });
|
|
||||||
}
|
|
||||||
for (auto [value, label] : Array<int&, const char*>{
|
|
||||||
{ tokenCount, MAX_TRANSLATIONS_IN_TIMESPAN },
|
|
||||||
{ rateLimitTimespan, TIMESPAN },
|
|
||||||
{ maxSentenceSize, MAX_SENTENCE_SIZE },
|
|
||||||
})
|
|
||||||
{
|
|
||||||
value = settings.value(label, value).toInt();
|
|
||||||
auto spinBox = new QSpinBox(this);
|
|
||||||
spinBox->setRange(0, INT_MAX);
|
|
||||||
spinBox->setValue(value);
|
|
||||||
display->addRow(label, spinBox);
|
|
||||||
connect(spinBox, qOverload<int>(&QSpinBox::valueChanged), [label, &value](int newValue) { settings.setValue(label, value = newValue); });
|
|
||||||
}
|
|
||||||
if (GET_API_KEY_FROM)
|
|
||||||
{
|
|
||||||
auto keyEdit = new QLineEdit(settings.value(API_KEY).toString(), this);
|
|
||||||
tlp->authKey = S(keyEdit->text());
|
|
||||||
QObject::connect(keyEdit, &QLineEdit::textChanged, [](QString key) { settings.setValue(API_KEY, S(tlp->authKey = S(key))); });
|
|
||||||
auto keyLabel = new QLabel(QString("<a href=\"%1\">%2</a>").arg(GET_API_KEY_FROM, API_KEY), this);
|
|
||||||
keyLabel->setOpenExternalLinks(true);
|
|
||||||
display->addRow(keyLabel, keyEdit);
|
|
||||||
}
|
|
||||||
|
|
||||||
setWindowTitle(TRANSLATION_PROVIDER);
|
|
||||||
QMetaObject::invokeMethod(this, &QWidget::show, Qt::QueuedConnection);
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
~Window()
|
case DLL_PROCESS_DETACH:
|
||||||
{
|
{
|
||||||
SaveCache();
|
SaveCache();
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
private:
|
|
||||||
void SaveTranslateTo(QString language)
|
|
||||||
{
|
|
||||||
SaveCache();
|
|
||||||
settings.setValue(TRANSLATE_TO, S(tlp->translateTo = S(language)));
|
|
||||||
LoadCache();
|
|
||||||
}
|
}
|
||||||
void SaveTranslateFrom(QString language)
|
return TRUE;
|
||||||
{
|
}
|
||||||
settings.setValue(TRANSLATE_FROM, S(tlp->translateFrom = S(language)));
|
|
||||||
}
|
|
||||||
} window;
|
|
||||||
|
|
||||||
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||||
{
|
{
|
||||||
@ -159,54 +80,34 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
|||||||
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(), [this](DWORD token) { return GetTickCount() - token > delay; }), 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;
|
const int tokenCount = 30, delay = 60 * 1000;
|
||||||
|
Synchronized<std::vector<DWORD>> tokens;
|
||||||
} rateLimiter;
|
} rateLimiter;
|
||||||
|
|
||||||
bool cache = false;
|
bool cache = false;
|
||||||
std::wstring translation;
|
std::wstring translation;
|
||||||
if (useFilter)
|
if (translationCache->count(sentence) != 0) translation = translationCache->at(sentence);
|
||||||
|
else if (!(rateLimiter.Request() || sentenceInfo["current select"])) translation = TOO_MANY_TRANS_REQUESTS;
|
||||||
|
else std::tie(cache, translation) = Translate(sentence);
|
||||||
|
if (cache && sentenceInfo["current select"])
|
||||||
{
|
{
|
||||||
Trim(sentence);
|
translationCache->insert({ sentence, translation });
|
||||||
sentence.erase(std::remove_if(sentence.begin(), sentence.end(), [](wchar_t ch) { return ch < ' ' && ch != '\n'; }), sentence.end());
|
if (translationCache->size() > savedSize + 50) SaveCache();
|
||||||
}
|
}
|
||||||
if (sentence.empty()) return true;
|
|
||||||
if (sentence.size() > maxSentenceSize) translation = SENTENCE_TOO_LARGE_TO_TRANS;
|
|
||||||
if (useCache)
|
|
||||||
{
|
|
||||||
auto translationCache = ::translationCache.Acquire();
|
|
||||||
if (auto it = translationCache->find(sentence); it != translationCache->end()) translation = it->second;
|
|
||||||
}
|
|
||||||
if (translation.empty() && (!translateSelectedOnly || sentenceInfo["current select"]))
|
|
||||||
if (rateLimiter.Request() || !useRateLimiter || (!rateLimitSelected && sentenceInfo["current select"])) std::tie(cache, translation) = Translate(sentence, tlp.Copy());
|
|
||||||
else translation = TOO_MANY_TRANS_REQUESTS;
|
|
||||||
if (cache) translationCache->operator[](sentence) = translation;
|
|
||||||
|
|
||||||
if (useFilter) Trim(translation);
|
Unescape(translation);
|
||||||
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
|
sentence += L"\n" + translation;
|
||||||
if (translation.empty()) translation = TRANSLATION_ERROR;
|
|
||||||
(sentence += L"\x200b \n") += translation;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
extern const std::unordered_map<std::wstring, std::wstring> codes;
|
|
||||||
TEST(
|
TEST(
|
||||||
{
|
assert(Translate(L"こんにちは").second.find(L"ello") != std::wstring::npos)
|
||||||
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"?"));
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
struct TranslationParam
|
|
||||||
{
|
|
||||||
std::wstring translateTo, translateFrom, authKey;
|
|
||||||
};
|
|
121
extensions/trie.h
Normal file
121
extensions/trie.h
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
template <typename C, typename V>
|
||||||
|
struct Trie
|
||||||
|
{
|
||||||
|
struct Node
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
std::basic_string<C> chars;
|
||||||
|
std::vector<std::pair<C, std::unique_ptr<Node>>> charMap;
|
||||||
|
};
|
||||||
|
uint64_t packedValue;
|
||||||
|
const C* Tail() const
|
||||||
|
{
|
||||||
|
return packedValue >> 63 ? chars.c_str() : nullptr;
|
||||||
|
}
|
||||||
|
V* Value() const
|
||||||
|
{
|
||||||
|
return (V*)((packedValue << 2) >> 2);
|
||||||
|
}
|
||||||
|
void SetValue(V value)
|
||||||
|
{
|
||||||
|
if (V* oldValue = Value()) *oldValue = std::move(value);
|
||||||
|
else packedValue = (1LL << (62 + (packedValue >> 63))) | (uint64_t)new V(std::move(value));
|
||||||
|
}
|
||||||
|
Node(bool map) :
|
||||||
|
packedValue(1LL << (62 + !map))
|
||||||
|
{
|
||||||
|
if (map) new (&charMap) decltype(charMap)();
|
||||||
|
else new (&chars) decltype(chars)();
|
||||||
|
}
|
||||||
|
~Node()
|
||||||
|
{
|
||||||
|
if (packedValue >> 63) chars.~basic_string();
|
||||||
|
else charMap.~vector();
|
||||||
|
delete Value();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Node>
|
||||||
|
static Node* Next(Node* node, C ch, bool makeMap = false)
|
||||||
|
{
|
||||||
|
if (node->packedValue >> 63) return nullptr;
|
||||||
|
auto it = std::lower_bound(node->charMap.begin(), node->charMap.end(), ch, [](const auto& one, auto two) { return one.first < two; });
|
||||||
|
if (it != node->charMap.end() && it->first == ch) return it->second.get();
|
||||||
|
if constexpr (!std::is_const_v<Node>) return node->charMap.insert(it, { ch, std::make_unique<Node>(makeMap) })->second.get();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Clear(Node* node)
|
||||||
|
{
|
||||||
|
if (node->packedValue >> 63) node->chars.clear();
|
||||||
|
else for (auto& [_, child] : node->charMap) Clear(child.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Node> root = std::make_unique<Node>(true);
|
||||||
|
|
||||||
|
Node* Insert(std::basic_string_view<C> key)
|
||||||
|
{
|
||||||
|
Node* current = root.get();
|
||||||
|
for (int i = 0; i < key.size(); ++i)
|
||||||
|
{
|
||||||
|
if (Node* next = Next(current, key[i], i + 1 == key.size())) current = next;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (current->chars.empty()) // FIXME: how to represent last character inside map?
|
||||||
|
{
|
||||||
|
current->chars = std::basic_string(key.begin() + i, key.end());
|
||||||
|
if(current->chars.empty())throw;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (current->chars == key.substr(i))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto oldChars = std::move(current->chars);
|
||||||
|
assert(current->Value());
|
||||||
|
auto oldValue = std::move(*current->Value());
|
||||||
|
auto keyRemaining = key.substr(i);
|
||||||
|
current->chars.~basic_string();
|
||||||
|
new (¤t->charMap) decltype(current->charMap)();
|
||||||
|
current->packedValue ^= 3ULL << 62;
|
||||||
|
for (i = 0; i < oldChars.size() && i < keyRemaining.size(); ++i)
|
||||||
|
{
|
||||||
|
if (oldChars[i] == keyRemaining[i]) current = Next(current, oldChars[i], true);
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
if (i == oldChars.size())
|
||||||
|
{
|
||||||
|
current->SetValue(std::move(oldValue));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto relocated = Next(current, oldChars[i]);
|
||||||
|
relocated->chars = oldChars.substr(i);
|
||||||
|
relocated->SetValue(std::move(oldValue));
|
||||||
|
}
|
||||||
|
if (i != keyRemaining.size()) (current = Next(current, keyRemaining[i]))->chars = std::basic_string(keyRemaining.begin() + i, keyRemaining.end());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Node* Root() const
|
||||||
|
{
|
||||||
|
return root.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Empty() const
|
||||||
|
{
|
||||||
|
return root->charMap.empty();
|
||||||
|
}
|
||||||
|
};
|
17
extensions/util.h
Normal file
17
extensions/util.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
inline std::wstring StringToWideString(const std::string& text)
|
||||||
|
{
|
||||||
|
std::vector<wchar_t> buffer(text.size() + 1);
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size());
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::string WideStringToString(const std::wstring& text)
|
||||||
|
{
|
||||||
|
std::vector<char> buffer((text.size() + 1) * 4);
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size(), nullptr, nullptr);
|
||||||
|
return buffer.data();
|
||||||
|
}
|
@ -1,4 +0,0 @@
|
|||||||
# 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)
|
|
||||||
target_precompile_headers(TextractorCLI REUSE_FROM pch)
|
|
||||||
target_link_libraries(TextractorCLI host)
|
|
@ -1,8 +0,0 @@
|
|||||||
add_library(host
|
|
||||||
host.cpp
|
|
||||||
textthread.cpp
|
|
||||||
hookcode.cpp
|
|
||||||
)
|
|
||||||
target_precompile_headers(host REUSE_FROM pch)
|
|
||||||
|
|
||||||
add_subdirectory(CLI)
|
|
@ -1,9 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
namespace HookCode
|
|
||||||
{
|
|
||||||
std::optional<HookParam> Parse(std::wstring code);
|
|
||||||
std::wstring Generate(HookParam hp, DWORD processId = 0);
|
|
||||||
}
|
|
@ -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,8 +15,8 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <shared_mutex>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <filesystem>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|
||||||
@ -27,15 +26,22 @@ constexpr bool x64 = true;
|
|||||||
constexpr bool x64 = false;
|
constexpr bool x64 = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
template <typename T, typename... Xs> struct ArrayImpl { using Type = std::tuple<T, Xs...>[]; };
|
template <typename T, typename... Xs>
|
||||||
template <typename T> struct ArrayImpl<T> { using Type = T[]; };
|
struct ArrayImpl { using type = std::tuple<T, Xs...>[]; };
|
||||||
template <typename... Ts> using Array = typename ArrayImpl<Ts...>::Type;
|
template <typename T>
|
||||||
|
struct ArrayImpl<T> { using type = T[]; };
|
||||||
|
template <typename... Ts>
|
||||||
|
using Array = typename ArrayImpl<Ts...>::type;
|
||||||
|
|
||||||
template <auto F> using Functor = std::integral_constant<std::remove_reference_t<decltype(F)>, F>; // shouldn't need remove_reference_t but MSVC is bugged
|
template <auto F> using Functor = std::integral_constant<std::decay_t<decltype(F)>, F>;
|
||||||
|
|
||||||
|
template <typename V>
|
||||||
|
struct Identity { V operator()(V v) const { return v; } };
|
||||||
|
|
||||||
struct PermissivePointer
|
struct PermissivePointer
|
||||||
{
|
{
|
||||||
template <typename T> operator T*() { return (T*)p; }
|
template <typename T>
|
||||||
|
operator T*() { return (T*)p; }
|
||||||
void* p;
|
void* p;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -69,37 +75,24 @@ public:
|
|||||||
|
|
||||||
Locker Acquire() { return { std::unique_lock(m), contents }; }
|
Locker Acquire() { return { std::unique_lock(m), contents }; }
|
||||||
Locker operator->() { return Acquire(); }
|
Locker operator->() { return Acquire(); }
|
||||||
T Copy() { return Acquire().contents; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
T contents;
|
T contents;
|
||||||
M m;
|
M m;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename F>
|
static struct
|
||||||
void SpawnThread(const F& f) // works in DllMain unlike std thread
|
|
||||||
{
|
{
|
||||||
F* copy = new F(f);
|
BYTE DUMMY[100];
|
||||||
CloseHandle(CreateThread(nullptr, 0, [](void* copy)
|
template <typename T>
|
||||||
{
|
operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
||||||
(*(F*)copy)();
|
|
||||||
delete (F*)copy;
|
|
||||||
return 0UL;
|
|
||||||
}, copy, 0, nullptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
inline struct
|
|
||||||
{
|
|
||||||
inline static BYTE DUMMY[100];
|
|
||||||
template <typename T> operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
|
||||||
} DUMMY;
|
} DUMMY;
|
||||||
|
|
||||||
inline auto Swallow = [](auto&&...) {};
|
template <typename T>
|
||||||
|
inline auto FormatArg(T arg) { return arg; }
|
||||||
|
|
||||||
template <typename T> std::optional<std::remove_cv_t<T>> Copy(T* ptr) { if (ptr) return *ptr; return {}; }
|
template <typename C>
|
||||||
|
inline auto FormatArg(const std::basic_string<C>& arg) { return arg.c_str(); }
|
||||||
template <typename T> inline auto FormatArg(T arg) { return arg; }
|
|
||||||
template <typename C> inline auto FormatArg(const std::basic_string<C>& arg) { return arg.c_str(); }
|
|
||||||
|
|
||||||
#pragma warning(push)
|
#pragma warning(push)
|
||||||
#pragma warning(disable: 4996)
|
#pragma warning(disable: 4996)
|
||||||
@ -120,44 +113,11 @@ 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)
|
|
||||||
{
|
|
||||||
std::vector<wchar_t> buffer(text.size() + 1);
|
|
||||||
if (int length = MultiByteToWideChar(encoding, 0, text.c_str(), text.size() + 1, buffer.data(), buffer.size()))
|
|
||||||
return std::wstring(buffer.data(), length - 1);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::wstring StringToWideString(const std::string& text)
|
|
||||||
{
|
|
||||||
std::vector<wchar_t> buffer(text.size() + 1);
|
|
||||||
MultiByteToWideChar(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size());
|
|
||||||
return buffer.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::string WideStringToString(const std::wstring& text)
|
|
||||||
{
|
|
||||||
std::vector<char> buffer((text.size() + 1) * 4);
|
|
||||||
WideCharToMultiByte(CP_UTF8, 0, text.c_str(), -1, buffer.data(), buffer.size(), nullptr, nullptr);
|
|
||||||
return buffer.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Args>
|
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>
|
|
||||||
inline void TEXTRACTOR_DEBUG(const wchar_t* format, const Args&... args) { SpawnThread([=] { TEXTRACTOR_MESSAGE(format, args...); }); }
|
|
||||||
|
|
||||||
void Localize();
|
|
||||||
|
|
||||||
#ifdef _DEBUG
|
#ifdef _DEBUG
|
||||||
#define TEST(...) static auto _ = CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr)
|
#define TEST(...) static auto _ = CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr);
|
||||||
#else
|
#else
|
||||||
#define TEST(...)
|
#define TEST(...)
|
||||||
#endif
|
#endif
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
// 8/23/2013 jichi
|
// 8/23/2013 jichi
|
||||||
// Branch: ITH/common.h, rev 128
|
// Branch: ITH/common.h, rev 128
|
||||||
|
|
||||||
enum { STRING = 12, MESSAGE_SIZE = 500, PIPE_BUFFER_SIZE = 50000, SHIFT_JIS = 932, MAX_MODULE_SIZE = 120, PATTERN_SIZE = 30, HOOK_NAME_SIZE = 60, FIXED_SPLIT_VALUE = 0x10001 };
|
enum { STRING = 12, MESSAGE_SIZE = 500, PIPE_BUFFER_SIZE = 10000, SHIFT_JIS = 932, MAX_MODULE_SIZE = 120, PATTERN_SIZE = 30, HOOK_NAME_SIZE = 30, FIXED_SPLIT_VALUE = 0x10001 };
|
||||||
enum WildcardByte { XX = 0x11 };
|
enum WildcardByte { XX = 0x11 };
|
||||||
|
|
||||||
enum HostCommandType { HOST_COMMAND_NEW_HOOK, HOST_COMMAND_REMOVE_HOOK, HOST_COMMAND_FIND_HOOK, HOST_COMMAND_MODIFY_HOOK, HOST_COMMAND_HIJACK_PROCESS, HOST_COMMAND_DETACH };
|
enum HostCommandType { HOST_COMMAND_NEW_HOOK, HOST_COMMAND_REMOVE_HOOK, HOST_COMMAND_FIND_HOOK, HOST_COMMAND_MODIFY_HOOK, HOST_COMMAND_HIJACK_PROCESS, HOST_COMMAND_DETACH };
|
||||||
@ -27,8 +27,8 @@ enum HookParamType : unsigned
|
|||||||
FIXING_SPLIT = 0x800,
|
FIXING_SPLIT = 0x800,
|
||||||
DIRECT_READ = 0x1000, // /R read code instead of classic /H hook code
|
DIRECT_READ = 0x1000, // /R read code instead of classic /H hook code
|
||||||
FULL_STRING = 0x2000,
|
FULL_STRING = 0x2000,
|
||||||
HEX_DUMP = 0x4000,
|
BLOCK_FLOOD = 0x4000, // remove this hook if flooding text causing perf issues
|
||||||
HOOK_ENGINE = 0x8000,
|
HEX_DUMP = 0x8000,
|
||||||
HOOK_ADDITIONAL = 0x10000,
|
HOOK_ENGINE = 0x10000,
|
||||||
KNOWN_UNSTABLE = 0x20000,
|
HOOK_ADDITIONAL = 0x20000,
|
||||||
};
|
};
|
||||||
|
@ -24,6 +24,10 @@ constexpr auto PIPE_AVAILABLE_EVENT = L"TEXTRACTOR_PIPE_AVAILABLE";
|
|||||||
// Files
|
// Files
|
||||||
|
|
||||||
constexpr auto ITH_DLL = L"texthook"; // .dll but LoadLibrary automatically adds that
|
constexpr auto ITH_DLL = L"texthook"; // .dll but LoadLibrary automatically adds that
|
||||||
constexpr auto& GAME_CONFIG_FILE = L"TextractorConfig.txt";
|
constexpr auto CONFIG_FILE = u8"Textractor.ini";
|
||||||
|
|
||||||
|
// Misc
|
||||||
|
|
||||||
|
constexpr auto WINDOW = u8"Window";
|
||||||
|
|
||||||
// EOF
|
// EOF
|
||||||
|
@ -1,26 +0,0 @@
|
|||||||
#include <Psapi.h>
|
|
||||||
|
|
||||||
inline std::optional<std::wstring> GetModuleFilename(DWORD processId, HMODULE module = NULL)
|
|
||||||
{
|
|
||||||
std::vector<wchar_t> buffer(MAX_PATH);
|
|
||||||
if (AutoHandle<> process = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, processId))
|
|
||||||
if (GetModuleFileNameExW(process, module, buffer.data(), MAX_PATH)) return buffer.data();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::optional<std::wstring> GetModuleFilename(HMODULE module = NULL)
|
|
||||||
{
|
|
||||||
std::vector<wchar_t> buffer(MAX_PATH);
|
|
||||||
if (GetModuleFileNameW(module, buffer.data(), MAX_PATH)) return buffer.data();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
inline std::vector<std::pair<DWORD, std::optional<std::wstring>>> GetAllProcesses()
|
|
||||||
{
|
|
||||||
std::vector<DWORD> processIds(10000);
|
|
||||||
DWORD spaceUsed = 0;
|
|
||||||
EnumProcesses(processIds.data(), 10000 * sizeof(DWORD), &spaceUsed);
|
|
||||||
std::vector<std::pair<DWORD, std::optional<std::wstring>>> processes;
|
|
||||||
for (int i = 0; i < spaceUsed / sizeof(DWORD); ++i) processes.push_back({ processIds[i], GetModuleFilename(processIds[i]) });
|
|
||||||
return processes;
|
|
||||||
}
|
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QHash>
|
#include <QHash>
|
||||||
@ -9,7 +10,6 @@
|
|||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QApplication>
|
|
||||||
#include <QLayout>
|
#include <QLayout>
|
||||||
#include <QFormLayout>
|
#include <QFormLayout>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
@ -22,15 +22,10 @@
|
|||||||
|
|
||||||
static thread_local bool ok;
|
static thread_local bool ok;
|
||||||
|
|
||||||
constexpr auto CONFIG_FILE = u8"Textractor.ini";
|
|
||||||
constexpr auto WINDOW = u8"Window";
|
|
||||||
|
|
||||||
struct Settings : QSettings { Settings(QObject* parent = nullptr) : QSettings(CONFIG_FILE, QSettings::IniFormat, parent) {} };
|
|
||||||
struct QTextFile : QFile { QTextFile(QString name, QIODevice::OpenMode mode) : QFile(name) { open(mode | QIODevice::Text); } };
|
struct QTextFile : QFile { QTextFile(QString name, QIODevice::OpenMode mode) : QFile(name) { open(mode | QIODevice::Text); } };
|
||||||
struct Localizer { Localizer() { Localize(); } };
|
|
||||||
inline std::wstring S(const QString& s) { return { s.toStdWString() }; }
|
inline std::wstring S(const QString& s) { return { s.toStdWString() }; }
|
||||||
inline QString S(const std::string& s) { return QString::fromStdString(s); }
|
inline QString S(const std::string& s) { return QString::fromStdString(s); }
|
||||||
inline QString S(const std::wstring& s) { return QString::fromStdWString(s); }
|
inline QString S(const std::wstring& s) { return QString::fromStdWString(s); }
|
||||||
// TODO: allow paired surrogates
|
// TODO: allow paired surrogates
|
||||||
inline void sanitize(QString& s) { s.chop(std::distance(std::remove_if(s.begin(), s.end(), [](QChar ch) { return ch.isSurrogate(); }), s.end())); }
|
inline void sanitize(QString& s) { s.chop(std::distance(std::remove_if(s.begin(), s.end(), [](QChar ch) { return ch.isSurrogate(); }), s.end())); }
|
||||||
inline QString sanitize(QString&& s) { sanitize(s); return std::move(s); }
|
inline QString sanitize(QString&& s) { QString result; for (auto ch : s) if (!ch.isSurrogate()) result.push_back(ch); return result; }
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include "const.h"
|
#include "const.h"
|
||||||
|
|
||||||
class WinMutex // Like CMutex but works with scoped_lock
|
class WinMutex // Like CMutex but works with scoped_lock
|
||||||
@ -62,7 +63,7 @@ struct SearchParam
|
|||||||
BYTE pattern[PATTERN_SIZE] = { x64 ? 0xcc : 0x55, x64 ? 0xcc : 0x8b, x64 ? 0x48 : 0xec, 0x89 }; // pattern in memory to search for
|
BYTE pattern[PATTERN_SIZE] = { x64 ? 0xcc : 0x55, x64 ? 0xcc : 0x8b, x64 ? 0x48 : 0xec, 0x89 }; // pattern in memory to search for
|
||||||
int length = x64 ? 4 : 3, // length of pattern (zero means this SearchParam is invalid and the default should be used)
|
int length = x64 ? 4 : 3, // length of pattern (zero means this SearchParam is invalid and the default should be used)
|
||||||
offset = x64 ? 2 : 0, // offset from start of pattern to add hook
|
offset = x64 ? 2 : 0, // offset from start of pattern to add hook
|
||||||
searchTime = 30000, // ms
|
searchTime = 20000, // ms
|
||||||
maxRecords = 100000,
|
maxRecords = 100000,
|
||||||
codepage = SHIFT_JIS;
|
codepage = SHIFT_JIS;
|
||||||
uintptr_t padding = 0, // same as hook param padding
|
uintptr_t padding = 0, // same as hook param padding
|
||||||
|
@ -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
|
||||||
|
|
||||||
@ -25,8 +24,6 @@ Name: "id"; MessagesFile: "compiler:Languages\Unofficial\Indonesian.isl"
|
|||||||
Name: "pt"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl"
|
Name: "pt"; MessagesFile: "compiler:Languages\BrazilianPortuguese.isl"
|
||||||
Name: "th"; MessagesFile: "compiler:Languages\Unofficial\Thai.isl"
|
Name: "th"; MessagesFile: "compiler:Languages\Unofficial\Thai.isl"
|
||||||
Name: "ko"; MessagesFile: "compiler:Languages\Unofficial\Korean.isl"
|
Name: "ko"; MessagesFile: "compiler:Languages\Unofficial\Korean.isl"
|
||||||
Name: "it"; MessagesFile: "compiler:Languages\Italian.isl"
|
|
||||||
Name: "fr"; MessagesFile: "compiler:Languages\French.isl"
|
|
||||||
|
|
||||||
|
|
||||||
[Files]
|
[Files]
|
||||||
@ -40,6 +37,3 @@ Source: "Builds\Textractor-Indonesian-{#VERSION}\*"; DestDir: "{app}"; Languages
|
|||||||
Source: "Builds\Textractor-Portuguese-{#VERSION}\*"; DestDir: "{app}"; Languages: pt; Flags: recursesubdirs ignoreversion
|
Source: "Builds\Textractor-Portuguese-{#VERSION}\*"; DestDir: "{app}"; Languages: pt; Flags: recursesubdirs ignoreversion
|
||||||
Source: "Builds\Textractor-Thai-{#VERSION}\*"; DestDir: "{app}"; Languages: th; Flags: recursesubdirs ignoreversion
|
Source: "Builds\Textractor-Thai-{#VERSION}\*"; DestDir: "{app}"; Languages: th; Flags: recursesubdirs ignoreversion
|
||||||
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-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";
|
|
||||||
|
@ -4,6 +4,4 @@ find_qt5(Core Widgets)
|
|||||||
|
|
||||||
add_executable(Test WIN32 main.cpp resource.rc)
|
add_executable(Test WIN32 main.cpp resource.rc)
|
||||||
|
|
||||||
target_precompile_headers(Test REUSE_FROM pch)
|
|
||||||
|
|
||||||
target_link_libraries(Test Qt5::Widgets)
|
target_link_libraries(Test Qt5::Widgets)
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
#include "resource.h"
|
#include "common.h"
|
||||||
|
#include "defs.h"
|
||||||
|
#include "resource.h"
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
@ -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 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)
|
||||||
|
@ -2244,6 +2244,7 @@ void InsertRealliveHook()
|
|||||||
//ConsoleOutput("Probably Reallive. Wait for text.");
|
//ConsoleOutput("Probably Reallive. Wait for text.");
|
||||||
ConsoleOutput("vnreng: TRIGGER Reallive");
|
ConsoleOutput("vnreng: TRIGGER Reallive");
|
||||||
trigger_fun = InsertRealliveDynamicHook;
|
trigger_fun = InsertRealliveDynamicHook;
|
||||||
|
SetTrigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace { // unnamed
|
namespace { // unnamed
|
||||||
@ -4494,8 +4495,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 +4509,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 +4632,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 +5735,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
|
||||||
@ -5799,11 +5804,12 @@ bool InsertShinaHook()
|
|||||||
{
|
{
|
||||||
int ver = GetShinaRioVersion();
|
int ver = GetShinaRioVersion();
|
||||||
if (ver >= 50) {
|
if (ver >= 50) {
|
||||||
|
SetTrigger();
|
||||||
//trigger_fun = StackSearchingTrigger<GetGlyphOutlineA, NULL>;
|
//trigger_fun = StackSearchingTrigger<GetGlyphOutlineA, NULL>;
|
||||||
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 +5823,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 +5831,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;
|
||||||
@ -5979,11 +5986,10 @@ bool InsertWaffleDynamicHook(LPVOID addr, DWORD frame, DWORD stack)
|
|||||||
* Sample game: 完全時間停止 体験版
|
* Sample game: 完全時間停止 体験版
|
||||||
* GDI text: TextOutA and GetTextExtentPoint32A
|
* GDI text: TextOutA and GetTextExtentPoint32A
|
||||||
*/
|
*/
|
||||||
bool InsertWaffleHook()
|
void InsertWaffleHook()
|
||||||
{
|
{
|
||||||
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;
|
||||||
@ -5993,42 +5999,15 @@ bool InsertWaffleHook()
|
|||||||
hp.type = DATA_INDIRECT|USING_SPLIT;
|
hp.type = DATA_INDIRECT|USING_SPLIT;
|
||||||
ConsoleOutput("vnreng: INSERT WAFFLE");
|
ConsoleOutput("vnreng: INSERT WAFFLE");
|
||||||
NewHook(hp, "WAFFLE");
|
NewHook(hp, "WAFFLE");
|
||||||
found = true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** new waffle?
|
|
||||||
* test on 母三人とアナあそび https://vndb.org/v24214
|
|
||||||
* and 変態エルフ姉妹と真面目オーク https://vndb.org/v24215
|
|
||||||
* and いかにして俺の妻は孕んだか…… https://vndb.org/v26205
|
|
||||||
* and 俺の知らぬ間に彼女が… https://vndb.org/v27781
|
|
||||||
*/
|
|
||||||
const BYTE bytes[] = {
|
|
||||||
0x50, //50 push eax
|
|
||||||
0x8b, 0xce, //8BCE mov ecx,esi
|
|
||||||
0xc6, 0x45, 0xfc, XX, //C645 FC 01 move byte ptr ss:[ebp-4],?
|
|
||||||
0x89, 0x75, 0xd4, //8975 D4 move dword ptr ss:[ebp-0x2c],esi
|
|
||||||
0xe8, XX4, //E8 ?? call ??
|
|
||||||
0x8d, 0x45, 0xdc //8D45 DC lea eax,dword ptr ss:[ebp-0x24]
|
|
||||||
};
|
|
||||||
if (DWORD addr = MemDbg::findBytes(bytes, sizeof(bytes), processStartAddress, processStopAddress))
|
|
||||||
{
|
|
||||||
HookParam hp = {};
|
|
||||||
hp.address = addr;
|
|
||||||
hp.offset = pusha_eax_off - 4;
|
|
||||||
hp.index = 0x00;
|
|
||||||
hp.length_offset = 1;
|
|
||||||
hp.type = DATA_INDIRECT;
|
|
||||||
ConsoleOutput("Textractor: INSERT WAFFLE2");
|
|
||||||
NewHook(hp, "WAFFLE2");
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
//ConsoleOutput("Probably Waffle. Wait for text.");
|
//ConsoleOutput("Probably Waffle. Wait for text.");
|
||||||
if (!found) trigger_fun = InsertWaffleDynamicHook;
|
trigger_fun = InsertWaffleDynamicHook;
|
||||||
return found;
|
SetTrigger();
|
||||||
//ConsoleOutput("vnreng:WAFFLE: failed");
|
//ConsoleOutput("vnreng:WAFFLE: failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InsertTinkerBellHook()
|
void InsertTinkerBellHook()
|
||||||
{
|
{
|
||||||
//DWORD s1,s2,i;
|
//DWORD s1,s2,i;
|
||||||
//DWORD ch=0x8141;
|
//DWORD ch=0x8141;
|
||||||
@ -6063,9 +6042,7 @@ bool InsertTinkerBellHook()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (count) return true;
|
|
||||||
ConsoleOutput("vnreng:TinkerBell: failed");
|
ConsoleOutput("vnreng:TinkerBell: failed");
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// s1=SearchPattern(processStartAddress,processStopAddress-processStartAddress-4,&ch,4);
|
// s1=SearchPattern(processStartAddress,processStopAddress-processStartAddress-4,&ch,4);
|
||||||
@ -6609,173 +6586,6 @@ bool InsertNitroplusHook()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Jazzinghen 23/05/2020: Add TokyoNecro hook
|
|
||||||
*
|
|
||||||
* [Nitroplus] 東京Necro 1.01 - Text boxes hook
|
|
||||||
*
|
|
||||||
* Hook code: HS-14*8@B5420:TokyoNecro.exe
|
|
||||||
*
|
|
||||||
* Debug method:
|
|
||||||
* Found memory location where the text was written, then used hardware break on write.
|
|
||||||
* After that found the function that writes the text in, found that the memory pointed
|
|
||||||
* contains more than just the text. Followed the call stack "upwards" until a function
|
|
||||||
* that handles only the text copy is found.
|
|
||||||
*
|
|
||||||
* Disassembled code:
|
|
||||||
* TokyoNecro.exe+B5420 - 55 - push ebp ; place to hook
|
|
||||||
* TokyoNecro.exe+B5421 - 8B EC - mov ebp,esp
|
|
||||||
* TokyoNecro.exe+B5423 - 6A FF - push -01
|
|
||||||
* TokyoNecro.exe+B5425 - 68 E8613000 - push TokyoNecro.exe+1961E8
|
|
||||||
* TokyoNecro.exe+B542A - 64 A1 00000000 - mov eax,fs:[00000000]
|
|
||||||
* TokyoNecro.exe+B5430 - 50 - push eax
|
|
||||||
* TokyoNecro.exe+B5431 - 64 89 25 00000000 - mov fs:[00000000],esp
|
|
||||||
* TokyoNecro.exe+B5438 - 83 EC 1C - sub esp,1C
|
|
||||||
* TokyoNecro.exe+B543B - 8B 55 08 - mov edx,[ebp+08]
|
|
||||||
* TokyoNecro.exe+B543E - 53 - push ebx
|
|
||||||
* TokyoNecro.exe+B543F - 56 - push esi
|
|
||||||
* TokyoNecro.exe+B5440 - 8B C2 - mov eax,edx
|
|
||||||
* TokyoNecro.exe+B5442 - 57 - push edi
|
|
||||||
* TokyoNecro.exe+B5443 - 8B D9 - mov ebx,ecx
|
|
||||||
* TokyoNecro.exe+B5445 - C7 45 EC 0F000000 - mov [ebp-14],0000000F
|
|
||||||
* TokyoNecro.exe+B544C - C7 45 E8 00000000 - mov [ebp-18],00000000
|
|
||||||
* TokyoNecro.exe+B5453 - C6 45 D8 00 - mov byte ptr [ebp-28],00
|
|
||||||
* TokyoNecro.exe+B5457 - 8D 70 01 - lea esi,[eax+01]
|
|
||||||
* TokyoNecro.exe+B545A - 8D 9B 00000000 - lea ebx,[ebx+00000000]
|
|
||||||
* TokyoNecro.exe+B5460 - 8A 08 - mov cl,[eax]
|
|
||||||
* TokyoNecro.exe+B5462 - 40 - inc eax
|
|
||||||
* TokyoNecro.exe+B5463 - 84 C9 - test cl,cl
|
|
||||||
* TokyoNecro.exe+B5465 - 75 F9 - jne TokyoNecro.exe+B5460
|
|
||||||
* TokyoNecro.exe+B5467 - 2B C6 - sub eax,esi
|
|
||||||
* TokyoNecro.exe+B5469 - 52 - push edx
|
|
||||||
* TokyoNecro.exe+B546A - 8B F8 - mov edi,eax ▷ Search
|
|
||||||
* TokyoNecro.exe+B546C - 8D 75 D8 - lea esi,[ebp-28] |
|
|
||||||
* TokyoNecro.exe+B546F - E8 6CE1F4FF - call TokyoNecro.exe+35E0 ▷
|
|
||||||
*
|
|
||||||
* Notes:
|
|
||||||
*
|
|
||||||
* There's more data above due to the fact that the start of the function is very
|
|
||||||
* common and it was hooking a wrong function.
|
|
||||||
*
|
|
||||||
* The text is contained into the memory location at [esp+04] when hooking the
|
|
||||||
* code at TokyoNecro.exe+B5420
|
|
||||||
*
|
|
||||||
* If the game is hooked right at the main menu it will also catch the real time clock
|
|
||||||
* rendered there.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace TokyoNecro {
|
|
||||||
|
|
||||||
const BYTE funcSig[] = { 0x55, 0x8b, 0xec };
|
|
||||||
|
|
||||||
bool TextHook() {
|
|
||||||
|
|
||||||
const BYTE bytecodes[] = {
|
|
||||||
0x8B, 0xF8, // 8B F8 - mov edi,eax
|
|
||||||
0x8D, 0x75, 0xD8, // 8D 75 D8 - lea esi,[ebp-28]
|
|
||||||
0xE8, 0x6C, 0xE1, 0xF4, 0xFF, // E8 6CE1F4FF - call TokyoNecro.exe+35E0
|
|
||||||
};
|
|
||||||
ULONG addr = MemDbg::findBytes(bytecodes, sizeof(bytecodes), processStartAddress, processStopAddress);
|
|
||||||
if (addr == 0) {
|
|
||||||
ConsoleOutput("Textractor:TokyoNecro: pattern not found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for the start of the function
|
|
||||||
const ULONG function_start = MemDbg::findEnclosingAlignedFunction(addr);
|
|
||||||
if (memcmp((void*)function_start, funcSig, sizeof(funcSig)) != 0) {
|
|
||||||
ConsoleOutput("Textractor: TokyoNecro: function start not found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HookParam hp = {};
|
|
||||||
hp.address = function_start;
|
|
||||||
// The memory address is held at [ebp+08] at TokyoNecro.exe+B543B, meaning that at
|
|
||||||
// the start of the function it's right above the stack pointer. Since there's no
|
|
||||||
// way to do an operation on the value of a register BEFORE dereferencing (e.g.
|
|
||||||
// (void*)(esp+4) instead of ((void*)esp)+4) we have to go up the stack instead of
|
|
||||||
// using the data in the registers
|
|
||||||
hp.offset = 0x4;
|
|
||||||
hp.type = USING_STRING;
|
|
||||||
ConsoleOutput("Textractor: INSERT TokyoNecroText");
|
|
||||||
NewHook(hp, "TokyoNecroText");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* [Nitroplus] 東京Necro 1.01 - Database/Encyclopedia hook
|
|
||||||
*
|
|
||||||
* Hook code: HS4*@B5380:tokyonecro.exe
|
|
||||||
*
|
|
||||||
* TokyoNecro.exe+B5380 - 55 - push ebp ; Location to hook
|
|
||||||
* TokyoNecro.exe+B5381 - 8B EC - mov ebp,esp
|
|
||||||
* TokyoNecro.exe+B5383 - 6A FF - push -01
|
|
||||||
* TokyoNecro.exe+B5385 - 68 E8618E00 - push TokyoNecro.exe+1961E8
|
|
||||||
* TokyoNecro.exe+B538A - 64 A1 00000000 - mov eax,fs:[00000000]
|
|
||||||
* TokyoNecro.exe+B5390 - 50 - push eax
|
|
||||||
* TokyoNecro.exe+B5391 - 64 89 25 00000000 - mov fs:[00000000],esp
|
|
||||||
* TokyoNecro.exe+B5398 - 83 EC 1C - sub esp,1C
|
|
||||||
* TokyoNecro.exe+B539B - 8B 55 08 - mov edx,[ebp+08]
|
|
||||||
* TokyoNecro.exe+B539E - 53 - push ebx
|
|
||||||
* TokyoNecro.exe+B539F - 56 - push esi
|
|
||||||
* TokyoNecro.exe+B53A0 - 8B C2 - mov eax,edx
|
|
||||||
* TokyoNecro.exe+B53A2 - 57 - push edi
|
|
||||||
* TokyoNecro.exe+B53A3 - 8B D9 - mov ebx,ecx
|
|
||||||
* TokyoNecro.exe+B53A5 - C7 45 EC 0F000000 - mov [ebp-14],0000000F
|
|
||||||
* TokyoNecro.exe+B53AC - C7 45 E8 00000000 - mov [ebp-18],00000000
|
|
||||||
* TokyoNecro.exe+B53B3 - C6 45 D8 00 - mov byte ptr [ebp-28],00
|
|
||||||
* TokyoNecro.exe+B53B7 - 8D 70 01 - lea esi,[eax+01]
|
|
||||||
* TokyoNecro.exe+B53BA - 8D 9B 00000000 - lea ebx,[ebx+00000000]
|
|
||||||
* TokyoNecro.exe+B53C0 - 8A 08 - mov cl,[eax]
|
|
||||||
* TokyoNecro.exe+B53C2 - 40 - inc eax
|
|
||||||
* TokyoNecro.exe+B53C3 - 84 C9 - test cl,cl
|
|
||||||
* TokyoNecro.exe+B53C5 - 75 F9 - jne TokyoNecro.exe+B53C0
|
|
||||||
* TokyoNecro.exe+B53C7 - 2B C6 - sub eax,esi
|
|
||||||
* TokyoNecro.exe+B53C9 - 52 - push edx
|
|
||||||
* TokyoNecro.exe+B53CA - 8B F8 - mov edi,eax ▷ Search
|
|
||||||
* TokyoNecro.exe+B53CC - 8D 75 D8 - lea esi,[ebp-28] |
|
|
||||||
* TokyoNecro.exe+B53CF - E8 0CE2F4FF - call TokyoNecro.exe+35E0 ▷
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
bool DatabaseHook()
|
|
||||||
{
|
|
||||||
const BYTE bytecodes[] = {
|
|
||||||
0x8B, 0xF8, // 8B F8 - mov edi,eax
|
|
||||||
0x8D, 0x75, 0xD8, // 8D 75 D8 - lea esi,[ebp-28]
|
|
||||||
0xE8, 0x0C, 0xE2, 0xF4, 0xFF, // E8 6CE1F4FF - call TokyoNecro.exe+35E0
|
|
||||||
};
|
|
||||||
ULONG addr = MemDbg::findBytes(bytecodes, sizeof(bytecodes), processStartAddress, processStopAddress);
|
|
||||||
if (addr == 0) {
|
|
||||||
ConsoleOutput("vnreng:TokyoNecro: pattern not found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for the start of the function
|
|
||||||
const ULONG function_start = MemDbg::findEnclosingAlignedFunction(addr);
|
|
||||||
if (memcmp((void*)function_start, funcSig, sizeof(funcSig)) != 0) {
|
|
||||||
ConsoleOutput("Textractor: TokyoNecro: function start not found");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HookParam hp = {};
|
|
||||||
hp.address = function_start;
|
|
||||||
hp.offset = 0x4;
|
|
||||||
hp.type = USING_STRING;
|
|
||||||
NewHook(hp, "TokyoNecroDatabase");
|
|
||||||
ConsoleOutput("vnreng: INSERT TokyoNecroDatabase");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace TokyoNecro
|
|
||||||
|
|
||||||
bool InsertTokyoNecroHook()
|
|
||||||
{
|
|
||||||
TokyoNecro::DatabaseHook();
|
|
||||||
return TokyoNecro::TextHook();
|
|
||||||
}
|
|
||||||
|
|
||||||
// jichi 6/21/2015
|
// jichi 6/21/2015
|
||||||
namespace { // unnamed
|
namespace { // unnamed
|
||||||
|
|
||||||
@ -8783,6 +8593,7 @@ bool InsertSystemAoiDynamic()
|
|||||||
ConsoleOutput("vnreng: DYNAMIC SystemAoi");
|
ConsoleOutput("vnreng: DYNAMIC SystemAoi");
|
||||||
//ConsoleOutput("Probably SoftHouseChara. Wait for text.");
|
//ConsoleOutput("Probably SoftHouseChara. Wait for text.");
|
||||||
trigger_fun = InsertSystemAoiDynamicHook;
|
trigger_fun = InsertSystemAoiDynamicHook;
|
||||||
|
SetTrigger();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9069,6 +8880,7 @@ void InsertIronGameSystemHook()
|
|||||||
{
|
{
|
||||||
//ConsoleOutput("Probably IronGameSystem. Wait for text.");
|
//ConsoleOutput("Probably IronGameSystem. Wait for text.");
|
||||||
trigger_fun = InsertIGSDynamicHook;
|
trigger_fun = InsertIGSDynamicHook;
|
||||||
|
SetTrigger();
|
||||||
ConsoleOutput("vnreng: TRIGGER IronGameSystem");
|
ConsoleOutput("vnreng: TRIGGER IronGameSystem");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9600,31 +9412,7 @@ static bool InsertNewWillPlusHook()
|
|||||||
NewHook(hp, "WillPlus2");
|
NewHook(hp, "WillPlus2");
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
/*
|
if (!found) ConsoleOutput("Textractor: WillPlus2: failed to find instructions");
|
||||||
hook cmp esi,0x3000
|
|
||||||
Sample games:
|
|
||||||
https://vndb.org/r54549
|
|
||||||
https://vndb.org/v22705
|
|
||||||
https://vndb.org/v24852
|
|
||||||
https://vndb.org/v25719
|
|
||||||
https://vndb.org/v27227
|
|
||||||
https://vndb.org/v27385
|
|
||||||
*/
|
|
||||||
const BYTE pattern[] =
|
|
||||||
{
|
|
||||||
0x81,0xfe,0x00,0x30,0x00,0x00 //81FE 00300000 cmp esi,0x3000
|
|
||||||
};
|
|
||||||
for (auto addr : Util::SearchMemory(pattern, sizeof(pattern), PAGE_EXECUTE, processStartAddress, processStopAddress))
|
|
||||||
{
|
|
||||||
HookParam hp = {};
|
|
||||||
hp.address = addr;
|
|
||||||
hp.type = USING_UNICODE;
|
|
||||||
hp.offset = pusha_esi_off - 4;
|
|
||||||
hp.length_offset = 1;
|
|
||||||
NewHook(hp, "WillPlus3");
|
|
||||||
found = true;
|
|
||||||
}
|
|
||||||
if (!found) ConsoleOutput("Textractor: WillPlus: failed to find instructions");
|
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -9729,6 +9517,7 @@ void InsertRyokuchaHook()
|
|||||||
{
|
{
|
||||||
//ConsoleOutput("Probably Ryokucha. Wait for text.");
|
//ConsoleOutput("Probably Ryokucha. Wait for text.");
|
||||||
trigger_fun = InsertRyokuchaDynamicHook;
|
trigger_fun = InsertRyokuchaDynamicHook;
|
||||||
|
SetTrigger();
|
||||||
ConsoleOutput("vnreng: TRIGGER Ryokucha");
|
ConsoleOutput("vnreng: TRIGGER Ryokucha");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -10088,7 +9877,7 @@ BYTE JIS_tableL[0x80] = {
|
|||||||
0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x00,
|
0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x00,
|
||||||
};
|
};
|
||||||
|
|
||||||
void SpecialHookAnex86(DWORD esp_base, HookParam*, BYTE, DWORD *data, DWORD *split, DWORD *len)
|
void SpecialHookAnex86(DWORD esp_base, HookParam *hp, BYTE, DWORD *data, DWORD *split, DWORD *len)
|
||||||
{
|
{
|
||||||
__asm
|
__asm
|
||||||
{
|
{
|
||||||
@ -10127,28 +9916,19 @@ _fin:
|
|||||||
} // unnamed namespace
|
} // unnamed namespace
|
||||||
bool InsertAnex86Hook()
|
bool InsertAnex86Hook()
|
||||||
{
|
{
|
||||||
const BYTE bytes[] = {
|
const DWORD dwords[] = {0x618ac033,0x0d418a0c}; // jichi 12/25/2013: Remove static keyword
|
||||||
0x8a, XX, 0x0c, // mov ??,[ecx+0C]
|
for (DWORD i = processStartAddress + 0x1000; i < processStopAddress - 8; i++)
|
||||||
0x8a, XX, 0x0d // mov ??,[ecx+0D]
|
if (*(DWORD *)i == dwords[0])
|
||||||
};
|
if (*(DWORD *)(i + 4) == dwords[1]) {
|
||||||
bool found = false;
|
|
||||||
for (auto addr : Util::SearchMemory(bytes, sizeof(bytes), PAGE_EXECUTE, processStartAddress, processStopAddress)) {
|
|
||||||
//const DWORD dwords[] = {0x618ac033,0x0d418a0c}; // jichi 12/25/2013: Remove static keyword
|
|
||||||
//for (DWORD i = processStartAddress + 0x1000; i < processStopAddress - 8; i++)
|
|
||||||
//if (*(DWORD *)i == dwords[0])
|
|
||||||
//if (*(DWORD *)(i + 4) == dwords[1]) {
|
|
||||||
HookParam hp = {};
|
HookParam hp = {};
|
||||||
if (*(BYTE*)(addr - 2) == 0x33 || *(BYTE*)(addr - 2) == 0x31) addr = addr - 2;
|
hp.address = i;
|
||||||
hp.address = addr;
|
|
||||||
hp.offset = pusha_ecx_off - 4;
|
|
||||||
hp.text_fun = SpecialHookAnex86;
|
hp.text_fun = SpecialHookAnex86;
|
||||||
//hp.type = EXTERN_HOOK;
|
//hp.type = EXTERN_HOOK;
|
||||||
hp.length_offset = 1;
|
hp.length_offset = 1;
|
||||||
ConsoleOutput("vnreng: INSERT Anex86");
|
ConsoleOutput("vnreng: INSERT Anex86");
|
||||||
NewHook(hp, "Anex86");
|
NewHook(hp, "Anex86");
|
||||||
found = true;
|
return true;
|
||||||
}
|
}
|
||||||
if (found) return true;
|
|
||||||
ConsoleOutput("vnreng:Anex86: failed");
|
ConsoleOutput("vnreng:Anex86: failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -10774,59 +10554,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 +13790,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,??
|
||||||
@ -16443,19 +16169,19 @@ bool InsertShinyDaysGameHook()
|
|||||||
0xff,0x83,0x70,0x03,0x00,0x00,0x33,0xf6,
|
0xff,0x83,0x70,0x03,0x00,0x00,0x33,0xf6,
|
||||||
0xc6,0x84,0x24,0x90,0x02,0x00,0x00,0x02
|
0xc6,0x84,0x24,0x90,0x02,0x00,0x00,0x02
|
||||||
};
|
};
|
||||||
|
LPVOID addr = (LPVOID)0x42ad94;
|
||||||
for (auto addr : Util::SearchMemory(bytes, sizeof(bytes))) {
|
if (::memcmp(addr, bytes, sizeof(bytes)) != 0) {
|
||||||
HookParam hp = {};
|
ConsoleOutput("vnreng:ShinyDays: only work for 1.00");
|
||||||
hp.address = addr + 0x8;
|
return false;
|
||||||
hp.text_fun = SpecialGameHookShinyDays;
|
|
||||||
hp.type = USING_UNICODE | USING_STRING | NO_CONTEXT;
|
|
||||||
ConsoleOutput("Textractor: INSERT ShinyDays");
|
|
||||||
NewHook(hp, "ShinyDays");
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConsoleOutput("Textractor:ShinyDays: pattern not found");
|
HookParam hp = {};
|
||||||
return false;
|
hp.address = 0x42ad9c;
|
||||||
|
hp.text_fun = SpecialGameHookShinyDays;
|
||||||
|
hp.type = USING_UNICODE|USING_STRING|NO_CONTEXT;
|
||||||
|
ConsoleOutput("vnreng: INSERT ShinyDays");
|
||||||
|
NewHook(hp, "ShinyDays 1.00");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0 // disabled as lova does not allow module from being modified
|
#if 0 // disabled as lova does not allow module from being modified
|
||||||
@ -16945,40 +16671,30 @@ bool InsertAdobeFlash10Hook()
|
|||||||
*/
|
*/
|
||||||
bool InsertRenpyHook()
|
bool InsertRenpyHook()
|
||||||
{
|
{
|
||||||
wchar_t python[] = L"python2X.dll", libpython[] = L"libpython2.X.dll";
|
for (int pythonMinorVersion = 0; pythonMinorVersion <= 8; ++pythonMinorVersion)
|
||||||
for (wchar_t* name : { python, libpython })
|
{
|
||||||
{
|
wchar_t python[] = L"python2X.dll";
|
||||||
wchar_t* pos = wcschr(name, L'X');
|
python[7] = L'0' + pythonMinorVersion;
|
||||||
for (int pythonMinorVersion = 0; pythonMinorVersion <= 8; ++pythonMinorVersion)
|
if (HMODULE module = GetModuleHandleW(python))
|
||||||
{
|
{
|
||||||
*pos = L'0' + pythonMinorVersion;
|
wcscpy_s(spDefault.exportModule, python);
|
||||||
if (HMODULE module = GetModuleHandleW(name))
|
HookParam hp = {};
|
||||||
{
|
hp.address = (DWORD)GetProcAddress(module, "PyUnicodeUCS2_Format");
|
||||||
wcscpy_s(spDefault.exportModule, name);
|
if (!hp.address)
|
||||||
HookParam hp = {};
|
{
|
||||||
hp.address = (DWORD)GetProcAddress(module, "PyUnicodeUCS2_Format");
|
ConsoleOutput("Textractor: Ren'py failed: failed to find PyUnicodeUCS2_Format");
|
||||||
if (!hp.address)
|
return false;
|
||||||
{
|
}
|
||||||
ConsoleOutput("Textractor: Ren'py failed: failed to find PyUnicodeUCS2_Format");
|
hp.offset = 4;
|
||||||
return false;
|
hp.index = 0xc;
|
||||||
}
|
hp.length_offset = 0;
|
||||||
hp.offset = 4;
|
hp.split = pusha_ebx_off - 4;
|
||||||
hp.index = 0xc;
|
hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT | USING_SPLIT;
|
||||||
hp.length_offset = 0;
|
//hp.filter_fun = [](void* str, auto, auto, auto) { return *(wchar_t*)str != L'%'; };
|
||||||
//hp.split = pusha_ebx_off - 4;
|
NewHook(hp, "Ren'py");
|
||||||
hp.text_fun = [](auto, auto, auto, DWORD* data, DWORD* split, DWORD* count)
|
return true;
|
||||||
{
|
}
|
||||||
*data = *(DWORD*)(*data + 0xc);
|
}
|
||||||
*count = wcslen((wchar_t*)*data) * sizeof(wchar_t);
|
|
||||||
*split = wcschr((wchar_t*)*data, L'%') == nullptr;
|
|
||||||
};
|
|
||||||
hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT/* | USING_SPLIT*/;
|
|
||||||
//hp.filter_fun = [](void* str, auto, auto, auto) { return *(wchar_t*)str != L'%'; };
|
|
||||||
NewHook(hp, "Ren'py");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll");
|
ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -17001,7 +16717,6 @@ 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");
|
|
||||||
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))
|
||||||
{
|
{
|
||||||
@ -17011,35 +16726,30 @@ void InsertMonoHook(HMODULE h)
|
|||||||
{
|
{
|
||||||
if (getJitInfo(domain, addr))
|
if (getJitInfo(domain, addr))
|
||||||
if (char* name = getName(addr))
|
if (char* name = getName(addr))
|
||||||
if (ShouldMonoHook(name))
|
if (strstr(name, "string:") && !strstr(name, "string:mem"))
|
||||||
{
|
{
|
||||||
HookParam hp = {};
|
HookParam hp = {};
|
||||||
hp.address = addr;
|
hp.address = addr;
|
||||||
hp.type = USING_UNICODE | FULL_STRING;
|
hp.type = USING_UNICODE | FULL_STRING | BLOCK_FLOOD;
|
||||||
if (!*loadedConfig) hp.type |= KNOWN_UNSTABLE;
|
|
||||||
hp.offset = 4;
|
hp.offset = 4;
|
||||||
char nameForUser[HOOK_NAME_SIZE] = {};
|
|
||||||
strncpy_s(nameForUser, name + 1, HOOK_NAME_SIZE - 1);
|
|
||||||
if (char* end = strstr(nameForUser, " + 0x0")) *end = 0;
|
|
||||||
if (char* end = strstr(nameForUser, "{")) *end = 0;
|
|
||||||
hp.text_fun = [](DWORD esp_base, HookParam*, BYTE, DWORD* data, DWORD* split, DWORD* len)
|
hp.text_fun = [](DWORD esp_base, HookParam*, BYTE, DWORD* data, DWORD* split, DWORD* len)
|
||||||
{
|
{
|
||||||
MonoString* string = (MonoString*)argof(1, esp_base);
|
MonoString* string = (MonoString*)argof(1, esp_base);
|
||||||
*data = (DWORD)string->chars;
|
*data = (DWORD)string->chars;
|
||||||
*len = string->length * 2;
|
*len = string->length * 2;
|
||||||
};
|
};
|
||||||
NewHook(hp, nameForUser);
|
NewHook(hp, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
__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");
|
|
||||||
return true;
|
return true;
|
||||||
failed:
|
failed:
|
||||||
ConsoleOutput("Textractor: Mono Dynamic failed");
|
ConsoleOutput("Textractor: Mono Dynamic failed");
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
SetTrigger();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** jichi 12/26/2014 Mono
|
/** jichi 12/26/2014 Mono
|
||||||
@ -17091,11 +16801,10 @@ 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;
|
|
||||||
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;
|
||||||
hp.text_fun = func.text_fun;
|
hp.text_fun = (decltype(hp.text_fun))func.text_fun;
|
||||||
ConsoleOutput("vnreng: Mono: INSERT");
|
ConsoleOutput("vnreng: Mono: INSERT");
|
||||||
NewHook(hp, func.functionName);
|
NewHook(hp, func.functionName);
|
||||||
ret = true;
|
ret = true;
|
||||||
@ -21031,14 +20740,6 @@ bool InsertTecmoPSPHook()
|
|||||||
*/
|
*/
|
||||||
bool InsertPCSX2Hooks()
|
bool InsertPCSX2Hooks()
|
||||||
{
|
{
|
||||||
memcpy(spDefault.pattern, Array<BYTE>{ 0x89, 0xc8, 0xc1, 0xe8, 0x0c }, spDefault.length = 5);
|
|
||||||
spDefault.minAddress = 0;
|
|
||||||
spDefault.maxAddress = -1ULL;
|
|
||||||
spDefault.offset = 0;
|
|
||||||
spDefault.searchTime = 60'000;
|
|
||||||
spDefault.maxRecords = 500'000;
|
|
||||||
spDefault.padding = 0x20000000;
|
|
||||||
ConsoleOutput("Textractor: PCSX2 detected (searching for hooks may work)");
|
|
||||||
// TODO: Add generic hooks
|
// TODO: Add generic hooks
|
||||||
return InsertTypeMoonPS2Hook()
|
return InsertTypeMoonPS2Hook()
|
||||||
|| InsertMarvelousPS2Hook()
|
|| InsertMarvelousPS2Hook()
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// engine/engine.h
|
// engine/engine.h
|
||||||
// 8/23/2013 jichi
|
// 8/23/2013 jichi
|
||||||
|
// See: http://ja.wikipedia.org/wiki/プロジェクト:美少女ゲーム系/ゲームエンジン
|
||||||
|
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
@ -14,7 +15,15 @@ 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 = "";
|
|
||||||
|
// Artikash 6/17/2019 TODO: These have the wrong values on x64
|
||||||
|
/** jichi 12/24/2014
|
||||||
|
* @param addr function address
|
||||||
|
* @param frame real address of the function, supposed to be the same as addr
|
||||||
|
* @param stack address of current stack - 4
|
||||||
|
* @return If success, which is reverted
|
||||||
|
*/
|
||||||
|
inline bool (*trigger_fun)(LPVOID addr, DWORD frame, DWORD stack);
|
||||||
|
|
||||||
bool InsertMonoHooks(); // Mono
|
bool InsertMonoHooks(); // Mono
|
||||||
|
|
||||||
@ -125,7 +134,6 @@ bool InsertNeXASHook(); // NeXAS: Thumbnail.pac
|
|||||||
bool InsertNextonHook(); // NEXTON: aInfo.db
|
bool InsertNextonHook(); // NEXTON: aInfo.db
|
||||||
bool InsertNexton1Hook();
|
bool InsertNexton1Hook();
|
||||||
bool InsertNitroplusHook(); // Nitroplus: *.npa
|
bool InsertNitroplusHook(); // Nitroplus: *.npa
|
||||||
bool InsertTokyoNecroHook(); // Nitroplus TokyoNecro: *.npk, resource string
|
|
||||||
bool InsertPalHook(); // AMUSE CRAFT: *.pac
|
bool InsertPalHook(); // AMUSE CRAFT: *.pac
|
||||||
bool InsertPensilHook(); // Pensil: PSetup.exe
|
bool InsertPensilHook(); // Pensil: PSetup.exe
|
||||||
bool InsertPONScripterHook();
|
bool InsertPONScripterHook();
|
||||||
@ -163,8 +171,8 @@ void InsertLucifenHook(); // Lucifen@Navel: *.lpk
|
|||||||
void InsertRyokuchaHook(); // Ryokucha: _checksum.exe
|
void InsertRyokuchaHook(); // Ryokucha: _checksum.exe
|
||||||
void InsertRealliveHook(); // RealLive: RealLive*.exe
|
void InsertRealliveHook(); // RealLive: RealLive*.exe
|
||||||
void InsertStuffScriptHook(); // Stuff: *.mpk
|
void InsertStuffScriptHook(); // Stuff: *.mpk
|
||||||
bool InsertTinkerBellHook(); // TinkerBell: arc00.dat
|
void InsertTinkerBellHook(); // TinkerBell: arc00.dat
|
||||||
bool InsertWaffleHook(); // WAFFLE: cg.pak
|
void InsertWaffleHook(); // WAFFLE: cg.pak
|
||||||
|
|
||||||
// CIRCUS: avdata/
|
// CIRCUS: avdata/
|
||||||
bool InsertCircusHook1();
|
bool InsertCircusHook1();
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#include "match.h"
|
#include "match.h"
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "defs.h"
|
|
||||||
#include "native/pchooks.h"
|
#include "native/pchooks.h"
|
||||||
|
|
||||||
extern const char* HIJACK_ERROR;
|
extern const char* HIJACK_ERROR;
|
||||||
@ -13,8 +12,6 @@ namespace Engine
|
|||||||
WCHAR* processName, // cached
|
WCHAR* processName, // cached
|
||||||
processPath[MAX_PATH]; // cached
|
processPath[MAX_PATH]; // cached
|
||||||
|
|
||||||
char configFileData[1000]{};
|
|
||||||
|
|
||||||
bool UnsafeDetermineEngineType();
|
bool UnsafeDetermineEngineType();
|
||||||
|
|
||||||
// jichi 10/21/2014: Return whether found the game engine
|
// jichi 10/21/2014: Return whether found the game engine
|
||||||
@ -33,27 +30,17 @@ namespace Engine
|
|||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD InsertDynamicHook(LPVOID addr, DWORD frame, DWORD stack)
|
||||||
|
{
|
||||||
|
return trigger_fun ? !trigger_fun(addr, frame, stack) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
void Hijack()
|
void Hijack()
|
||||||
{
|
{
|
||||||
static auto _ = ([]
|
static auto _ = []
|
||||||
{
|
{
|
||||||
GetModuleFileNameW(nullptr, processPath, MAX_PATH);
|
GetModuleFileNameW(nullptr, processPath, MAX_PATH);
|
||||||
processName = wcsrchr(processPath, L'\\') + 1;
|
processName = wcsrchr(processPath, L'\\') + 1;
|
||||||
wchar_t configFilename[MAX_PATH + std::size(GAME_CONFIG_FILE)];
|
|
||||||
wcsncpy_s(configFilename, processPath, MAX_PATH - 1);
|
|
||||||
wcscpy_s(wcsrchr(configFilename, L'\\') + 1, std::size(GAME_CONFIG_FILE), GAME_CONFIG_FILE);
|
|
||||||
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);
|
|
||||||
if (strnicmp(configFileData, "engine:", 7) == 0)
|
|
||||||
{
|
|
||||||
if (const char* config = strchr(configFileData, '\n')) *(char*)(loadedConfig = config)++ = 0;
|
|
||||||
ConsoleOutput("Textractor: Engine = %s", requestedEngine = strlwr(configFileData + 7));
|
|
||||||
}
|
|
||||||
else loadedConfig = configFileData;
|
|
||||||
if (!*loadedConfig || strstr(configFileData, "https://")) loadedConfig = "";
|
|
||||||
else ConsoleOutput("Textractor: game configuration loaded");
|
|
||||||
}
|
|
||||||
|
|
||||||
processStartAddress = processStopAddress = (uintptr_t)GetModuleHandleW(nullptr);
|
processStartAddress = processStopAddress = (uintptr_t)GetModuleHandleW(nullptr);
|
||||||
MEMORY_BASIC_INFORMATION info;
|
MEMORY_BASIC_INFORMATION info;
|
||||||
@ -67,19 +54,8 @@ 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!");
|
return NULL;
|
||||||
}(), 0);
|
}();
|
||||||
}
|
|
||||||
|
|
||||||
bool ShouldMonoHook(const char* name)
|
|
||||||
{
|
|
||||||
if (!*loadedConfig) return strstr(name, "string:") && !strstr(name, "string:mem");
|
|
||||||
for (const char* hook = loadedConfig; hook; hook = strchr(hook + 1, '\t'))
|
|
||||||
for (auto start = name; *start; ++start)
|
|
||||||
for (int i = 0; ; ++i)
|
|
||||||
if (start[i] != hook[i + 1]) break;
|
|
||||||
else if (!hook[i + 2] || hook[i + 2] == '\t') return true;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,10 @@ namespace Engine {
|
|||||||
|
|
||||||
// jichi 10/21/2014: Return whether found the engine
|
// jichi 10/21/2014: Return whether found the engine
|
||||||
void Hijack();
|
void Hijack();
|
||||||
|
void terminate();
|
||||||
|
|
||||||
bool ShouldMonoHook(const char* name);
|
// jichi 10/21/2014: Return 0 if failed
|
||||||
|
DWORD InsertDynamicHook(LPVOID addr, DWORD frame, DWORD stack);
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// match.cc
|
// match.cc
|
||||||
// 8/9/2013 jichi
|
// 8/9/2013 jichi
|
||||||
// Branch: ITH_Engine/engine.cpp, revision 133
|
// Branch: ITH_Engine/engine.cpp, revision 133
|
||||||
|
|
||||||
@ -72,14 +72,6 @@ bool DeterminePCEngine()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetModuleHandleW(L"GameAssembly.dll")) // TODO: is there a way to autofind hook?
|
|
||||||
{
|
|
||||||
ConsoleOutput("Textractor: Precompiled Unity found (searching for hooks should work)");
|
|
||||||
wcscpy_s(spDefault.boundaryModule, L"GameAssembly.dll");
|
|
||||||
spDefault.padding = 12;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PC games
|
// PC games
|
||||||
PcHooks::hookGDIFunctions();
|
PcHooks::hookGDIFunctions();
|
||||||
PcHooks::hookGDIPlusFunctions();
|
PcHooks::hookGDIPlusFunctions();
|
||||||
@ -281,7 +273,8 @@ bool DetermineEngineByFile2()
|
|||||||
InsertWaffleHook();
|
InsertWaffleHook();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (Util::CheckFile(L"Arc00.dat") && InsertTinkerBellHook()) {
|
if (Util::CheckFile(L"Arc00.dat")) {
|
||||||
|
InsertTinkerBellHook();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (Util::CheckFile(L"*.vfs")) { // jichi 7/6/2014: Better to test AoiLib.dll? ja.wikipedia.org/wiki/ソフトハウスキャラ
|
if (Util::CheckFile(L"*.vfs")) { // jichi 7/6/2014: Better to test AoiLib.dll? ja.wikipedia.org/wiki/ソフトハウスキャラ
|
||||||
@ -435,22 +428,6 @@ bool DetermineEngineByFile4()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// jichi 11/22/2015: 凍京NECRO 体験版
|
|
||||||
// Jazzinghen 23/05/2020: Add check for 凍京NECRO
|
|
||||||
// ResEdit shows multiple potential strings:
|
|
||||||
// - TOKYONECRO
|
|
||||||
// - 東京NECRO
|
|
||||||
// - TokyoNecro.exe in "OriginalFilename"
|
|
||||||
if (Util::CheckFile(L"*.npk")) {
|
|
||||||
if (Util::SearchResourceString(L"TOKYONECRO")) {
|
|
||||||
InsertTokyoNecroHook();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ConsoleOutput("vnreng: IGNORE new Nitroplus");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,9 +606,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;
|
||||||
@ -724,6 +702,12 @@ bool DetermineNoEngine()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// jichi 11/22/2015: 凍京NECRO 体験版
|
||||||
|
if (Util::CheckFile(L"*.npk")) {
|
||||||
|
ConsoleOutput("vnreng: IGNORE new Nitroplus");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// 8/29/2015 jichi: minori, text in GetGlyphOutlineA
|
// 8/29/2015 jichi: minori, text in GetGlyphOutlineA
|
||||||
if (Util::CheckFile(L"*.paz")) {
|
if (Util::CheckFile(L"*.paz")) {
|
||||||
ConsoleOutput("vnreng: IGNORE minori");
|
ConsoleOutput("vnreng: IGNORE minori");
|
||||||
|
@ -80,7 +80,6 @@ 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");
|
|
||||||
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) } })
|
||||||
@ -92,18 +91,13 @@ namespace Engine
|
|||||||
{
|
{
|
||||||
if (getJitInfo(domain, addr))
|
if (getJitInfo(domain, addr))
|
||||||
if (char* name = getName(addr))
|
if (char* name = getName(addr))
|
||||||
if (strstr(name, "0x0") && ShouldMonoHook(name))
|
if (strstr(name, "string:") && strstr(name, "+ 0x0") && !strstr(name, "string:mem"))
|
||||||
{
|
{
|
||||||
HookParam hp = {};
|
HookParam hp = {};
|
||||||
hp.address = addr;
|
hp.address = addr;
|
||||||
hp.type = USING_STRING | USING_UNICODE | FULL_STRING;
|
hp.type = USING_STRING | USING_UNICODE | BLOCK_FLOOD | FULL_STRING;
|
||||||
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] = {};
|
|
||||||
strncpy_s(nameForUser, name + 1, HOOK_NAME_SIZE - 1);
|
|
||||||
if (char* end = strstr(nameForUser, " + 0x0")) *end = 0;
|
|
||||||
if (char* end = strstr(nameForUser, "{")) *end = 0;
|
|
||||||
hp.length_fun = [](uintptr_t, uintptr_t data)
|
hp.length_fun = [](uintptr_t, uintptr_t data)
|
||||||
{
|
{
|
||||||
/* Artikash 6/18/2019:
|
/* Artikash 6/18/2019:
|
||||||
@ -112,19 +106,18 @@ namespace Engine
|
|||||||
int len = *(int*)(data - 4);
|
int len = *(int*)(data - 4);
|
||||||
return len > 0 && len < PIPE_BUFFER_SIZE ? len * 2 : 0;
|
return len > 0 && len < PIPE_BUFFER_SIZE ? len * 2 : 0;
|
||||||
};
|
};
|
||||||
NewHook(hp, nameForUser);
|
NewHook(hp, name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
__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");
|
|
||||||
return true;
|
return true;
|
||||||
failed:
|
failed:
|
||||||
ConsoleOutput("Textractor: Mono Dynamic failed");
|
ConsoleOutput("Textractor: Mono Dynamic failed");
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
SetTrigger();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,85 +125,25 @@ 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)
|
||||||
NewHook(hp, "JavaScript");
|
|
||||||
}
|
|
||||||
if (addr2)
|
|
||||||
{
|
|
||||||
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
|
|
||||||
*
|
|
||||||
* Sample games: https://vndb.org/v19843 https://vndb.org/v12038 and many more OELVNs
|
|
||||||
*
|
|
||||||
* Uses CPython, and links to python27.dll. PyUicodeUCS2_Format is the function used to process text.
|
|
||||||
* first argument. offset 0x18 from that is a wchar_t* to the actual string
|
|
||||||
* ebx seems to work well as the split param, not sure why
|
|
||||||
*/
|
|
||||||
bool InsertRenpyHook()
|
|
||||||
{
|
|
||||||
wchar_t python[] = L"python2X.dll", libpython[] = L"libpython2.X.dll";
|
|
||||||
for (wchar_t* name : { python, libpython })
|
|
||||||
{
|
|
||||||
wchar_t* pos = wcschr(name, L'X');
|
|
||||||
for (int pythonMinorVersion = 0; pythonMinorVersion <= 8; ++pythonMinorVersion)
|
|
||||||
{
|
{
|
||||||
*pos = L'0' + pythonMinorVersion;
|
int len = *(int*)(data - 4);
|
||||||
if (HMODULE module = GetModuleHandleW(name))
|
return len > 0 && len < PIPE_BUFFER_SIZE ? len * 2 : 0;
|
||||||
{
|
};
|
||||||
wcscpy_s(spDefault.exportModule, name);
|
NewHook(hp, "JavaScript");
|
||||||
HookParam hp = {};
|
return true;
|
||||||
hp.address = (DWORD)GetProcAddress(module, "PyUnicodeUCS2_Format");
|
|
||||||
if (!hp.address)
|
|
||||||
{
|
|
||||||
ConsoleOutput("Textractor: Ren'py failed: failed to find PyUnicodeUCS2_Format");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
hp.offset = -0x20; // rcx
|
|
||||||
hp.index = 0x18;
|
|
||||||
hp.length_offset = 0;
|
|
||||||
//hp.split = pusha_ebx_off - 4;
|
|
||||||
hp.type = USING_STRING | USING_UNICODE | NO_CONTEXT | DATA_INDIRECT /* | USING_SPLIT*/;
|
|
||||||
//hp.filter_fun = [](void* str, auto, auto, auto) { return *(wchar_t*)str != L'%'; };
|
|
||||||
NewHook(hp, "Ren'py");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ConsoleOutput("Textractor: Ren'py failed: failed to find python2X.dll");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,16 +153,6 @@ namespace Engine
|
|||||||
|
|
||||||
for (const wchar_t* moduleName : { (const wchar_t*)NULL, L"node.dll", L"nw.dll" }) if (InsertV8Hook(GetModuleHandleW(moduleName))) return true;
|
for (const wchar_t* moduleName : { (const wchar_t*)NULL, L"node.dll", L"nw.dll" }) if (InsertV8Hook(GetModuleHandleW(moduleName))) return true;
|
||||||
|
|
||||||
if (GetModuleHandleW(L"GameAssembly.dll")) // TODO: is there a way to autofind hook?
|
|
||||||
{
|
|
||||||
ConsoleOutput("Textractor: Precompiled Unity found (searching for hooks should work)");
|
|
||||||
wcscpy_s(spDefault.boundaryModule, L"GameAssembly.dll");
|
|
||||||
spDefault.padding = 20;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Util::CheckFile(L"*.py") && InsertRenpyHook()) return true;
|
|
||||||
|
|
||||||
for (const wchar_t* monoName : { L"mono.dll", L"mono-2.0-bdwgc.dll" }) if (HMODULE module = GetModuleHandleW(monoName)) if (InsertMonoHooks(module)) return true;
|
for (const wchar_t* monoName : { L"mono.dll", L"mono-2.0-bdwgc.dll" }) if (HMODULE module = GetModuleHandleW(monoName)) if (InsertMonoHooks(module)) return true;
|
||||||
|
|
||||||
for (std::wstring DXVersion : { L"d3dx9", L"d3dx10" })
|
for (std::wstring DXVersion : { L"d3dx9", L"d3dx10" })
|
||||||
|
@ -38,7 +38,7 @@ struct MonoFunction { // argument indices start from 0 for SpecialHookMonoString
|
|||||||
size_t textIndex; // argument index
|
size_t textIndex; // argument index
|
||||||
short lengthIndex; // argument index
|
short lengthIndex; // argument index
|
||||||
unsigned long hookType; // HookParam type
|
unsigned long hookType; // HookParam type
|
||||||
void(*text_fun)(DWORD stack, HookParam* hp, BYTE obsoleteAlwaysZero, DWORD* data, DWORD* split, DWORD* len); // HookParam::text_fun_t
|
void *text_fun; // HookParam::text_fun_t
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MONO_FUNCTIONS_INITIALIZER \
|
#define MONO_FUNCTIONS_INITIALIZER \
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// pchooks.cc
|
// pchooks.cc
|
||||||
// 8/1/2014 jichi
|
// 8/1/2014 jichi
|
||||||
|
|
||||||
#include "pchooks.h"
|
#include "pchooks.h"
|
||||||
@ -279,18 +279,11 @@ void PcHooks::hookOtherPcFunctions()
|
|||||||
// _Out_opt_ LPBOOL lpUsedDefaultChar
|
// _Out_opt_ LPBOOL lpUsedDefaultChar
|
||||||
// );
|
// );
|
||||||
|
|
||||||
// 2/29/2020 Artikash: TODO: Sort out what to do for string comparison functions
|
// 3/17/2014 jichi: Temporarily disabled
|
||||||
// http://sakuradite.com/topic/159
|
// http://sakuradite.com/topic/159
|
||||||
NEW_HOOK(L"kernel32.dll", MultiByteToWideChar, s_arg3, 0,4,0, USING_STRING, s_arg4 / arg_sz)
|
NEW_HOOK(L"kernel32.dll", MultiByteToWideChar, s_arg3, 0,4,0, USING_STRING, s_arg4 / arg_sz)
|
||||||
NEW_HOOK(L"kernel32.dll", WideCharToMultiByte, s_arg3, 0,4,0, USING_UNICODE|USING_STRING, s_arg4 / arg_sz)
|
NEW_HOOK(L"kernel32.dll", WideCharToMultiByte, s_arg3, 0,4,0, USING_UNICODE|USING_STRING, s_arg4 / arg_sz)
|
||||||
|
|
||||||
NEW_HOOK(L"kernel32.dll", GetStringTypeA, s_arg3, 0, 0, 0, USING_STRING, s_arg4 / arg_sz)
|
|
||||||
NEW_HOOK(L"kernel32.dll", GetStringTypeExA, s_arg3, 0, 0, 0, USING_STRING, s_arg4 / arg_sz)
|
|
||||||
NEW_HOOK(L"kernel32.dll", FoldStringA, s_arg2, 0, 0, 0, USING_STRING, s_arg3 / arg_sz)
|
|
||||||
NEW_HOOK(L"kernel32.dll", GetStringTypeW, s_arg2, 0, 0, 0, USING_UNICODE|USING_STRING, s_arg3 / arg_sz)
|
|
||||||
NEW_HOOK(L"kernel32.dll", GetStringTypeExW, s_arg3, 0, 0, 0, USING_UNICODE|USING_STRING, s_arg4 / arg_sz)
|
|
||||||
NEW_HOOK(L"kernel32.dll", FoldStringW, s_arg2, 0, 0, 0, USING_UNICODE|USING_STRING, s_arg3 / arg_sz)
|
|
||||||
|
|
||||||
NEW_HOOK(L"user32.dll", CharNextA, s_arg1, 0,0,0, USING_STRING|DATA_INDIRECT, 1) // LPTSTR WINAPI CharNext(_In_ LPCTSTR lpsz);
|
NEW_HOOK(L"user32.dll", CharNextA, s_arg1, 0,0,0, USING_STRING|DATA_INDIRECT, 1) // LPTSTR WINAPI CharNext(_In_ LPCTSTR lpsz);
|
||||||
NEW_HOOK(L"user32.dll", CharNextW, s_arg1, 0,0,0, USING_UNICODE|DATA_INDIRECT, 1)
|
NEW_HOOK(L"user32.dll", CharNextW, s_arg1, 0,0,0, USING_UNICODE|DATA_INDIRECT, 1)
|
||||||
NEW_HOOK(L"user32.dll", CharPrevA, s_arg1, 0,0,0, USING_STRING|DATA_INDIRECT, 1) // LPTSTR WINAPI CharPrev(_In_ LPCTSTR lpszStart, _In_ LPCTSTR lpszCurrent);
|
NEW_HOOK(L"user32.dll", CharPrevA, s_arg1, 0,0,0, USING_STRING|DATA_INDIRECT, 1) // LPTSTR WINAPI CharPrev(_In_ LPCTSTR lpszStart, _In_ LPCTSTR lpszCurrent);
|
||||||
@ -300,7 +293,7 @@ void PcHooks::hookOtherPcFunctions()
|
|||||||
if (HMODULE module = GetModuleHandleW(L"OLEAUT32.dll"))
|
if (HMODULE module = GetModuleHandleW(L"OLEAUT32.dll"))
|
||||||
{
|
{
|
||||||
NEW_MODULE_HOOK(module, SysAllocString, s_arg1, 0, 0, 0, USING_UNICODE|USING_STRING, 0)
|
NEW_MODULE_HOOK(module, SysAllocString, s_arg1, 0, 0, 0, USING_UNICODE|USING_STRING, 0)
|
||||||
NEW_MODULE_HOOK(module, SysAllocStringLen, s_arg1, 0, 0, 0, USING_UNICODE|USING_STRING|KNOWN_UNSTABLE, s_arg2 / arg_sz)
|
NEW_MODULE_HOOK(module, SysAllocStringLen, s_arg1, 0, 0, 0, USING_UNICODE|USING_STRING, s_arg2 / arg_sz)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,8 @@
|
|||||||
#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* STARTING_SEARCH;
|
||||||
extern const char* HOOK_SEARCH_INITIALIZING;
|
|
||||||
extern const char* HOOK_SEARCH_INITIALIZED;
|
extern const char* HOOK_SEARCH_INITIALIZED;
|
||||||
extern const char* MAKE_GAME_PROCESS_TEXT;
|
extern const char* MAKE_GAME_PROCESS_TEXT;
|
||||||
extern const char* HOOK_SEARCH_FINISHED;
|
extern const char* HOOK_SEARCH_FINISHED;
|
||||||
@ -21,7 +19,6 @@ namespace
|
|||||||
struct HookRecord
|
struct HookRecord
|
||||||
{
|
{
|
||||||
uint64_t address = 0;
|
uint64_t address = 0;
|
||||||
uintptr_t padding = 0;
|
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
char text[MAX_STRING_SIZE] = {};
|
char text[MAX_STRING_SIZE] = {};
|
||||||
};
|
};
|
||||||
@ -70,8 +67,8 @@ namespace
|
|||||||
// https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention
|
// https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention
|
||||||
// https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers
|
// https://stackoverflow.com/questions/43358429/save-value-of-xmm-registers
|
||||||
0x48, 0x83, 0xec, 0x20, // sub rsp,0x20
|
0x48, 0x83, 0xec, 0x20, // sub rsp,0x20
|
||||||
0xf3, 0x0f, 0x7f, 0x24, 0x24, // movdqu [rsp],xmm4
|
0xc5, 0xfa, 0x7f, 0x24, 0x24, // vmovdqu [rsp],xmm4
|
||||||
0xf3, 0x0f, 0x7f, 0x6c, 0x24, 0x10, // movdqu [rsp+0x10],xmm5
|
0xc5, 0xfa, 0x7f, 0x6c, 0x24, 0x10, // vmovdqu [rsp+0x10],xmm5
|
||||||
0x48, 0x8d, 0x8c, 0x24, 0xa8, 0x00, 0x00, 0x00, // lea rcx,[rsp+0xa8]
|
0x48, 0x8d, 0x8c, 0x24, 0xa8, 0x00, 0x00, 0x00, // lea rcx,[rsp+0xa8]
|
||||||
0x48, 0xba, 0,0,0,0,0,0,0,0, // mov rcx,@addr
|
0x48, 0xba, 0,0,0,0,0,0,0,0, // mov rcx,@addr
|
||||||
0x48, 0xb8, 0,0,0,0,0,0,0,0, // mov rax,@Send
|
0x48, 0xb8, 0,0,0,0,0,0,0,0, // mov rax,@Send
|
||||||
@ -79,8 +76,8 @@ namespace
|
|||||||
0x48, 0x83, 0xe4, 0xf0, // and rsp,0xfffffffffffffff0 ; align stack
|
0x48, 0x83, 0xe4, 0xf0, // and rsp,0xfffffffffffffff0 ; align stack
|
||||||
0xff, 0xd0, // call rax
|
0xff, 0xd0, // call rax
|
||||||
0x48, 0x89, 0xdc, // mov rsp,rbx
|
0x48, 0x89, 0xdc, // mov rsp,rbx
|
||||||
0xf3, 0x0f, 0x6f, 0x6c, 0x24, 0x10, // movdqu xmm5,XMMWORD PTR[rsp + 0x10]
|
0xc5, 0xfa, 0x6f, 0x6c, 0x24, 0x10, // vmovdqu xmm5,XMMWORD PTR[rsp + 0x10]
|
||||||
0xf3, 0x0f, 0x6f, 0x24, 0x24, // movdqu xmm4,XMMWORD PTR[rsp]
|
0xc5, 0xfa, 0x6f, 0x24, 0x24, // vmovdqu xmm4,XMMWORD PTR[rsp]
|
||||||
0x48, 0x83, 0xc4, 0x20, // add rsp,0x20
|
0x48, 0x83, 0xc4, 0x20, // add rsp,0x20
|
||||||
0x41, 0x5f, // pop r15
|
0x41, 0x5f, // pop r15
|
||||||
0x41, 0x5e, // pop r14
|
0x41, 0x5e, // pop r14
|
||||||
@ -99,7 +96,7 @@ namespace
|
|||||||
0x5b, // pop rbx
|
0x5b, // pop rbx
|
||||||
0x58, // pop rax
|
0x58, // pop rax
|
||||||
0x9d, // pop rflags
|
0x9d, // pop rflags
|
||||||
0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [rip]
|
0xff, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword ptr [0] ; relative to next instruction (i.e. jmp @original)
|
||||||
0,0,0,0,0,0,0,0 // @original
|
0,0,0,0,0,0,0,0 // @original
|
||||||
};
|
};
|
||||||
constexpr int addr_offset = 50, send_offset = 60, original_offset = 126, registers = 16;
|
constexpr int addr_offset = 50, send_offset = 60, original_offset = 126, registers = 16;
|
||||||
@ -137,9 +134,9 @@ void Send(char** stack, uintptr_t address)
|
|||||||
// it is unsafe to call ANY external functions from this, as they may have been hooked (if called the hook would call this function making an infinite loop)
|
// it is unsafe to call ANY external functions from this, as they may have been hooked (if called the hook would call this function making an infinite loop)
|
||||||
// the exceptions are compiler intrinsics like _InterlockedDecrement
|
// the exceptions are compiler intrinsics like _InterlockedDecrement
|
||||||
if (recordsAvailable <= 0) return;
|
if (recordsAvailable <= 0) return;
|
||||||
for (int i = -registers; i < 10; ++i) for (auto padding : { uintptr_t{}, sp.padding })
|
for (int i = -registers; i < 10; ++i)
|
||||||
{
|
{
|
||||||
char* str = stack[i] + padding;
|
char* str = stack[i] + sp.padding;
|
||||||
if (IsBadReadPtr(str) || IsBadReadPtr(str + MAX_STRING_SIZE)) continue;
|
if (IsBadReadPtr(str) || IsBadReadPtr(str + MAX_STRING_SIZE)) continue;
|
||||||
__try
|
__try
|
||||||
{
|
{
|
||||||
@ -158,7 +155,6 @@ void Send(char** stack, uintptr_t address)
|
|||||||
if (n < sp.maxRecords)
|
if (n < sp.maxRecords)
|
||||||
{
|
{
|
||||||
records[n].address = address;
|
records[n].address = address;
|
||||||
records[n].padding = padding;
|
|
||||||
records[n].offset = i * sizeof(char*);
|
records[n].offset = i * sizeof(char*);
|
||||||
for (int j = 0; j < length; ++j) records[n].text[j] = str[j];
|
for (int j = 0; j < length; ++j) records[n].text[j] = str[j];
|
||||||
records[n].text[length] = 0;
|
records[n].text[length] = 0;
|
||||||
@ -202,12 +198,12 @@ void SearchForHooks(SearchParam spUser)
|
|||||||
|
|
||||||
sp = spUser.length == 0 ? spDefault : spUser;
|
sp = spUser.length == 0 ? spDefault : spUser;
|
||||||
|
|
||||||
ConsoleOutput(HOOK_SEARCH_INITIALIZING, 0.);
|
|
||||||
do
|
do
|
||||||
try { records = std::make_unique<HookRecord[]>(recordsAvailable = sp.maxRecords); }
|
try { records = std::make_unique<HookRecord[]>(recordsAvailable = sp.maxRecords); }
|
||||||
catch (std::bad_alloc) { ConsoleOutput("Textractor: SearchForHooks ERROR: out of memory, retrying to allocate %d", sp.maxRecords /= 2); }
|
catch (std::bad_alloc) { ConsoleOutput("Textractor: SearchForHooks ERROR: out of memory, retrying to allocate %d", sp.maxRecords /= 2); }
|
||||||
while (!records && sp.maxRecords);
|
while (!records && sp.maxRecords);
|
||||||
|
|
||||||
|
ConsoleOutput(STARTING_SEARCH);
|
||||||
std::vector<uint64_t> addresses;
|
std::vector<uint64_t> addresses;
|
||||||
if (*sp.boundaryModule) std::tie(sp.minAddress, sp.maxAddress) = Util::QueryModuleLimits(GetModuleHandleW(sp.boundaryModule));
|
if (*sp.boundaryModule) std::tie(sp.minAddress, sp.maxAddress) = Util::QueryModuleLimits(GetModuleHandleW(sp.boundaryModule));
|
||||||
if (*sp.exportModule) addresses = GetFunctions((uintptr_t)GetModuleHandleW(sp.exportModule));
|
if (*sp.exportModule) addresses = GetFunctions((uintptr_t)GetModuleHandleW(sp.exportModule));
|
||||||
@ -224,13 +220,11 @@ void SearchForHooks(SearchParam spUser)
|
|||||||
MH_CreateHook((void*)addresses[i], trampolines[i], &original);
|
MH_CreateHook((void*)addresses[i], trampolines[i], &original);
|
||||||
MH_QueueEnableHook((void*)addresses[i]);
|
MH_QueueEnableHook((void*)addresses[i]);
|
||||||
memcpy(trampolines[i], trampoline, sizeof(trampoline));
|
memcpy(trampolines[i], trampoline, sizeof(trampoline));
|
||||||
*(uintptr_t*)(trampolines[i] + addr_offset) = addresses[i];
|
*(uintptr_t*)(trampolines[i] + addr_offset) = addresses[i];
|
||||||
*(void**)(trampolines[i] + original_offset) = original;
|
*(void**)(trampolines[i] + original_offset) = original;
|
||||||
if (i % 2500 == 0) ConsoleOutput(HOOK_SEARCH_INITIALIZING, 1 + 98. * i / addresses.size());
|
|
||||||
}
|
}
|
||||||
ConsoleOutput(HOOK_SEARCH_INITIALIZED, addresses.size());
|
ConsoleOutput(HOOK_SEARCH_INITIALIZED, addresses.size());
|
||||||
MH_ApplyQueued();
|
MH_ApplyQueued();
|
||||||
ConsoleOutput(HOOK_SEARCH_STARTING);
|
|
||||||
ConsoleOutput(MAKE_GAME_PROCESS_TEXT, sp.searchTime / 1000);
|
ConsoleOutput(MAKE_GAME_PROCESS_TEXT, sp.searchTime / 1000);
|
||||||
Sleep(sp.searchTime);
|
Sleep(sp.searchTime);
|
||||||
for (auto addr : addresses) MH_QueueDisableHook((void*)addr);
|
for (auto addr : addresses) MH_QueueDisableHook((void*)addr);
|
||||||
@ -246,7 +240,7 @@ void SearchForHooks(SearchParam spUser)
|
|||||||
hp.offset = records[i].offset;
|
hp.offset = records[i].offset;
|
||||||
hp.type = USING_UNICODE | USING_STRING;
|
hp.type = USING_UNICODE | USING_STRING;
|
||||||
hp.address = records[i].address;
|
hp.address = records[i].address;
|
||||||
hp.padding = records[i].padding;
|
hp.padding = sp.padding;
|
||||||
hp.codepage = sp.codepage;
|
hp.codepage = sp.codepage;
|
||||||
if (sp.hookPostProcessor) sp.hookPostProcessor(hp);
|
if (sp.hookPostProcessor) sp.hookPostProcessor(hp);
|
||||||
NotifyHookFound(hp, (wchar_t*)records[i].text);
|
NotifyHookFound(hp, (wchar_t*)records[i].text);
|
||||||
@ -265,7 +259,7 @@ void SearchForText(wchar_t* text, UINT codepage)
|
|||||||
char codepageText[PATTERN_SIZE * 4] = {};
|
char codepageText[PATTERN_SIZE * 4] = {};
|
||||||
WideCharToMultiByte(codepage, 0, text, PATTERN_SIZE, codepageText, PATTERN_SIZE * 4, nullptr, nullptr);
|
WideCharToMultiByte(codepage, 0, text, PATTERN_SIZE, codepageText, PATTERN_SIZE * 4, nullptr, nullptr);
|
||||||
if (strlen(utf8Text) < 4 || strlen(codepageText) < 4 || wcslen(text) < 4) return ConsoleOutput(NOT_ENOUGH_TEXT);
|
if (strlen(utf8Text) < 4 || strlen(codepageText) < 4 || wcslen(text) < 4) return ConsoleOutput(NOT_ENOUGH_TEXT);
|
||||||
ConsoleOutput(HOOK_SEARCH_STARTING);
|
ConsoleOutput(STARTING_SEARCH);
|
||||||
auto GenerateHooks = [&](std::vector<uint64_t> addresses, HookParamType type)
|
auto GenerateHooks = [&](std::vector<uint64_t> addresses, HookParamType type)
|
||||||
{
|
{
|
||||||
for (auto addr : addresses)
|
for (auto addr : addresses)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
void SearchForText(wchar_t* text, UINT codepage);
|
void SearchForText(wchar_t* text, UINT codepage);
|
||||||
|
@ -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;
|
||||||
@ -87,10 +86,13 @@ DWORD WINAPI Pipe(LPVOID)
|
|||||||
FreeLibraryAndExitThread(GetModuleHandleW(ITH_DLL), 0);
|
FreeLibraryAndExitThread(GetModuleHandleW(ITH_DLL), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextOutput(ThreadParam tp, BYTE (*buffer)[PIPE_BUFFER_SIZE], int len)
|
void TextOutput(ThreadParam tp, BYTE* text, int len)
|
||||||
{
|
{
|
||||||
if (len < 0 || len > PIPE_BUFFER_SIZE - sizeof(tp)) ConsoleOutput("Textractor: something went very wrong (invalid length %d at hook address %I64d)", len, tp.addr);
|
if (len < 0) return;
|
||||||
|
if (len > PIPE_BUFFER_SIZE - sizeof(tp)) len = PIPE_BUFFER_SIZE - sizeof(tp);
|
||||||
|
BYTE buffer[PIPE_BUFFER_SIZE] = {};
|
||||||
*(ThreadParam*)buffer = tp;
|
*(ThreadParam*)buffer = tp;
|
||||||
|
memcpy(buffer + sizeof(tp), text, len);
|
||||||
WriteFile(hookPipe, buffer, sizeof(tp) + len, DUMMY, nullptr);
|
WriteFile(hookPipe, buffer, sizeof(tp) + len, DUMMY, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,10 @@
|
|||||||
// 8/23/2013 jichi
|
// 8/23/2013 jichi
|
||||||
// Branch: ITH/IHF_DLL.h, rev 66
|
// Branch: ITH/IHF_DLL.h, rev 66
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
|
|
||||||
void TextOutput(ThreadParam tp, BYTE (*buffer)[PIPE_BUFFER_SIZE], int len);
|
void TextOutput(ThreadParam tp, BYTE* text, int len);
|
||||||
void ConsoleOutput(LPCSTR text, ...);
|
void ConsoleOutput(LPCSTR text, ...);
|
||||||
void NotifyHookFound(HookParam hp, wchar_t* text);
|
void NotifyHookFound(HookParam hp, wchar_t* text);
|
||||||
void NotifyHookRemove(uint64_t addr, LPCSTR name);
|
void NotifyHookRemove(uint64_t addr, LPCSTR name);
|
||||||
@ -15,6 +16,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)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user