Merge branch 'master' into pr/416-add-devtools-api-and-new-deepl-extension
This commit is contained in:
commit
3a34f989e5
12
CREDITS.md
12
CREDITS.md
@ -6,10 +6,11 @@ If you're on this list and want your link changed let me know.
|
||||
- [Niakr1s](https://github.com/Niakr1s)
|
||||
- [tinyAdapter](https://github.com/tinyAdapter)
|
||||
- [lgztx96](https://github.com/lgztx96)
|
||||
- with a special thanks to everyone reporting issues :)
|
||||
- French translation by Racky
|
||||
- [Jazzinghen](https://github.com/Jazzinghen)
|
||||
- [luojunyuan](https://github.com/luojunyuan)
|
||||
- French translation by Racky and [Gratusfr](https://github.com/Gratusfr)
|
||||
- Spanish translation by [scese250](https://github.com/scese250)
|
||||
- Turkish translation by niisokusu
|
||||
- Turkish translation by [niisokusu](https://reddit.com/u/niisokusu)
|
||||
- Simplified Chinese translation by [tinyAdapter](https://github.com/tinyAdapter) and [lgztx96](https://github.com/lgztx96)
|
||||
- Russian translation by [TokcDK](https://github.com/TokcDK)
|
||||
- Indonesian translation by [Hawxone](https://github.com/Hawxone)
|
||||
@ -19,8 +20,9 @@ If you're on this list and want your link changed let me know.
|
||||
- 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](http://www.hongfire.com/forum/member/325894-stomp)
|
||||
- VNR engine made by [jichi](https://archive.is/prJwr)
|
||||
- VNR engine made by [jichi](https://github.com/jichifly)
|
||||
- ITH updated by [Andys](https://github.com/AndyScull)
|
||||
- ITH originally made by [kaosu](http://www.hongfire.com/forum/member/562651-kaosu)
|
||||
- ITH originally made by [kaosu](https://code.google.com/archive/p/interactive-text-hooker)
|
||||
- Locale Emulator library made by [xupefei](https://github.com/xupefei)
|
||||
- MinHook library made by [TsudaKageyu](https://github.com/TsudaKageyu)
|
||||
- Last but not least, the many people that have reported issues. To everybody mentioned here: Thank You!
|
||||
|
@ -147,7 +147,7 @@ namespace
|
||||
{
|
||||
QMultiHash<QString, DWORD> allProcesses;
|
||||
for (auto [processId, processName] : GetAllProcesses())
|
||||
if (processName && (showSystemProcesses || processName->find(L":\\Windows\\") == std::wstring::npos))
|
||||
if (processName && (showSystemProcesses || processName->find(L":\\Windows\\") == std::string::npos))
|
||||
allProcesses.insert(QFileInfo(S(processName.value())).fileName(), processId);
|
||||
|
||||
QStringList processList(allProcesses.uniqueKeys());
|
||||
@ -165,7 +165,7 @@ namespace
|
||||
std::wstring path = std::wstring(process).erase(process.rfind(L'\\'));
|
||||
|
||||
PROCESS_INFORMATION info = {};
|
||||
auto useLocale = openSettings().value(CONFIG_JP_LOCALE, PROMPT).toInt();
|
||||
auto useLocale = Settings().value(CONFIG_JP_LOCALE, PROMPT).toInt();
|
||||
if (!x64 && (useLocale == ALWAYS || (useLocale == PROMPT && QMessageBox::question(This, SELECT_PROCESS, USE_JP_LOCALE) == QMessageBox::Yes)))
|
||||
{
|
||||
if (HMODULE localeEmulator = LoadLibraryW(L"LoaderDll"))
|
||||
@ -202,7 +202,7 @@ namespace
|
||||
{
|
||||
if (auto processName = GetModuleFilename(selectedProcessId)) if (int last = processName->rfind(L'\\') + 1)
|
||||
{
|
||||
std::wstring configFile = std::wstring(processName.value()).replace(last, std::wstring::npos, GAME_CONFIG_FILE);
|
||||
std::wstring configFile = std::wstring(processName.value()).replace(last, std::string::npos, GAME_CONFIG_FILE);
|
||||
if (!std::filesystem::exists(configFile)) QTextFile(S(configFile), QFile::WriteOnly).write("see https://github.com/Artikash/Textractor/wiki/Game-configuration-file");
|
||||
if (std::filesystem::exists(configFile)) _wspawnlp(_P_DETACH, L"notepad", L"notepad", configFile.c_str(), NULL);
|
||||
else QMessageBox::critical(This, GAME_CONFIG, QString(FAILED_TO_CREATE_CONFIG_FILE).arg(S(configFile)));
|
||||
@ -439,10 +439,10 @@ namespace
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void Settings()
|
||||
void OpenSettings()
|
||||
{
|
||||
QDialog dialog(This, Qt::WindowCloseButtonHint);
|
||||
QSettings settings(CONFIG_FILE, QSettings::IniFormat, &dialog);
|
||||
Settings settings(&dialog);
|
||||
QFormLayout layout(&dialog);
|
||||
QPushButton saveButton(SAVE_SETTINGS, &dialog);
|
||||
for (auto [value, label] : Array<bool&, const char*>{
|
||||
@ -501,7 +501,7 @@ namespace
|
||||
font.fromString(fontString);
|
||||
font.setStyleStrategy(QFont::NoFontMerging);
|
||||
ui.textOutput->setFont(font);
|
||||
QSettings(CONFIG_FILE, QSettings::IniFormat).setValue(FONT, font.toString());
|
||||
Settings().setValue(FONT, font.toString());
|
||||
}
|
||||
|
||||
void ProcessConnected(DWORD processId)
|
||||
@ -605,7 +605,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
{ REMOVE_HOOKS, RemoveHooks },
|
||||
{ SAVE_HOOKS, SaveHooks },
|
||||
{ SEARCH_FOR_HOOKS, FindHooks },
|
||||
{ SETTINGS, Settings },
|
||||
{ SETTINGS, OpenSettings },
|
||||
{ EXTENSIONS, Extensions }
|
||||
})
|
||||
{
|
||||
@ -623,7 +623,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
connect(ui.textOutput, &QPlainTextEdit::selectionChanged, this, CopyUnlessMouseDown);
|
||||
connect(ui.textOutput, &QPlainTextEdit::customContextMenuRequested, this, OutputContextMenu);
|
||||
|
||||
QSettings settings(CONFIG_FILE, QSettings::IniFormat);
|
||||
Settings settings;
|
||||
if (settings.contains(WINDOW) && QApplication::screenAt(settings.value(WINDOW).toRect().center())) setGeometry(settings.value(WINDOW).toRect());
|
||||
SetOutputFont(settings.value(FONT, ui.textOutput->font().toString()).toString());
|
||||
TextThread::filterRepetition = settings.value(FILTER_REPETITION, TextThread::filterRepetition).toBool();
|
||||
@ -649,7 +649,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
if (arg[1] == L'p' || arg[1] == L'P')
|
||||
if (DWORD processId = _wtoi(arg.substr(2).c_str())) Host::InjectProcess(processId);
|
||||
else for (auto [processId, processName] : processes)
|
||||
if (processName.value_or(L"").find(L"\\" + arg.substr(2)) != std::wstring::npos) Host::InjectProcess(processId);
|
||||
if (processName.value_or(L"").find(L"\\" + arg.substr(2)) != std::string::npos) Host::InjectProcess(processId);
|
||||
|
||||
std::thread([]
|
||||
{
|
||||
@ -672,7 +672,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
openSettings().setValue(WINDOW, geometry());
|
||||
Settings().setValue(WINDOW, geometry());
|
||||
CleanupExtensions();
|
||||
SetErrorMode(SEM_NOGPFAULTERRORBOX);
|
||||
ExitProcess(0);
|
||||
|
@ -91,12 +91,11 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
L"api.cognitive.microsofttranslator.com",
|
||||
L"POST",
|
||||
FormatString(L"/translate?api-version=3.0&to=%s", translateTo.Copy()).c_str(),
|
||||
FormatString(R"([{"text":"%s"}])", JSON::Escape(text)),
|
||||
FormatString(R"([{"text":"%s"}])", JSON::Escape(WideStringToString(text))),
|
||||
FormatString(L"Content-Type: application/json; charset=UTF-8\r\nOcp-Apim-Subscription-Key:%s", apiKey.Copy()).c_str()
|
||||
})
|
||||
{
|
||||
// Response formatted as JSON: translation starts with text":" and ends with ","to
|
||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"text\":\"(.+?)\",\""))) return { true, results[1] };
|
||||
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) };
|
||||
@ -107,8 +106,7 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
L"POST",
|
||||
FormatString(L"/ttranslatev3?fromLang=auto-detect&to=%s&text=%s", translateTo.Copy(), Escape(text)).c_str()
|
||||
})
|
||||
// Response formatted as JSON: translation starts with text":" and ends with ","to
|
||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"text\":\"(.+?)\",\""))) return { true, results[1] };
|
||||
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) };
|
||||
}
|
||||
|
@ -50,13 +50,14 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
L"Content-Type: application/x-www-form-urlencoded"
|
||||
})))
|
||||
// Response formatted as JSON: translation starts with text":" and ends with "}]
|
||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"text\":\"(.+?)\"\\}\\]"))) return { true, results[1] };
|
||||
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
|
||||
int64_t r = _time64(nullptr), n = std::count(text.begin(), text.end(), L'i') + 1;
|
||||
int id = 10000 * std::uniform_int_distribution(0, 9999)(std::mt19937(std::random_device()()));
|
||||
thread_local auto generator = std::mt19937(std::random_device()());
|
||||
int id = 10000 * std::uniform_int_distribution(0, 9999)(generator) + 1;
|
||||
// user_preferred_langs? what should priority be? does timestamp do anything? other translation quality options?
|
||||
auto body = FormatString(R"(
|
||||
{
|
||||
@ -80,21 +81,19 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
}]
|
||||
}
|
||||
}
|
||||
)", ++id, r + (n - r % n), translateTo.Copy(), JSON::Escape(text));
|
||||
)", id, r + (n - r % n), translateTo.Copy(), JSON::Escape(WideStringToString(text)));
|
||||
// missing accept-encoding header since it fucks up HttpRequest
|
||||
std::wstring headers = L"Host: www2.deepl.com\r\nAccept-Language: en-US,en;q=0.5\r\nContent-type: text/plain; charset=utf-8\r\nOrigin: https://www.deepl.com\r\nTE: Trailers";
|
||||
if (HttpRequest httpRequest{
|
||||
L"Mozilla/5.0 Textractor",
|
||||
L"www2.deepl.com",
|
||||
L"POST",
|
||||
L"/jsonrpc",
|
||||
body,
|
||||
headers.c_str(),
|
||||
L"Host: www2.deepl.com\r\nAccept-Language: en-US,en;q=0.5\r\nContent-type: application/json; charset=utf-8\r\nOrigin: https://www.deepl.com\r\nTE: Trailers",
|
||||
L"https://www.deepl.com/translator",
|
||||
WINHTTP_FLAG_SECURE
|
||||
})
|
||||
// Response formatted as JSON: translation starts with preprocessed_sentence":" and ends with ","
|
||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"postprocessed_sentence\":\"(.+?)\",\""))) return { true, results[1] };
|
||||
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) };
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ struct PrettyWindow : QDialog
|
||||
{
|
||||
PrettyWindow(const char* name)
|
||||
{
|
||||
localize();
|
||||
ui.setupUi(this);
|
||||
ui.display->setGraphicsEffect(&outliner);
|
||||
setWindowFlags(Qt::FramelessWindowHint);
|
||||
@ -76,7 +77,7 @@ struct PrettyWindow : QDialog
|
||||
|
||||
protected:
|
||||
QMenu menu{ ui.display };
|
||||
QSettings settings{ openSettings(this) };
|
||||
Settings settings{ this };
|
||||
|
||||
private:
|
||||
void RequestFont()
|
||||
@ -196,7 +197,7 @@ public:
|
||||
void AddSentence(QString sentence)
|
||||
{
|
||||
if (sentence.size() > maxSentenceSize) sentence = SENTENCE_TOO_BIG;
|
||||
if (!showOriginal) sentence = sentence.section('\n', sentence.count('\n') / 2 + 1);
|
||||
if (!showOriginal && sentence.contains(u8"\x200b \n")) sentence = sentence.split(u8"\x200b \n")[1];
|
||||
sanitize(sentence);
|
||||
sentence.chop(std::distance(std::remove(sentence.begin(), sentence.end(), QChar::Tabulation), sentence.end()));
|
||||
sentenceHistory.push_back(sentence);
|
||||
|
@ -125,37 +125,6 @@ QStringList languages
|
||||
bool translateSelectedOnly = false, rateLimitAll = true, rateLimitSelected = false, useCache = true;
|
||||
int tokenCount = 30, tokenRestoreDelay = 60000, maxSentenceSize = 500;
|
||||
|
||||
unsigned TKK = 0;
|
||||
|
||||
std::wstring GetTranslationUri(const std::wstring& text)
|
||||
{
|
||||
// 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.Copy(), Escape(text));
|
||||
|
||||
// reverse engineered from translate.google.com
|
||||
std::wstring escapedText;
|
||||
unsigned a = time(NULL) / 3600, b = a; // the first part of TKK
|
||||
for (unsigned char ch : WideStringToString(text))
|
||||
{
|
||||
escapedText += FormatString(L"%%%02X", (int)ch);
|
||||
a += ch;
|
||||
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.Copy(), 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 (!apiKey->empty())
|
||||
@ -164,31 +133,40 @@ std::pair<bool, std::wstring> Translate(const std::wstring& text)
|
||||
L"translation.googleapis.com",
|
||||
L"POST",
|
||||
FormatString(L"/language/translate/v2?format=text&target=%s&key=%s", translateTo.Copy(), apiKey.Copy()).c_str(),
|
||||
FormatString(R"({"q":["%s"]})", JSON::Escape(text))
|
||||
FormatString(R"({"q":["%s"]})", JSON::Escape(WideStringToString(text)))
|
||||
})
|
||||
if (auto translation = Copy(JSON::Parse(httpRequest.response)[L"data"][L"translations"][0][L"translatedText"].String())) return { true, translation.value() };
|
||||
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{
|
||||
L"Mozilla/5.0 Textractor",
|
||||
L"translate.google.com",
|
||||
L"POST",
|
||||
L"/_/TranslateWebserverUi/data/batchexecute?rpcids=MkEWBc",
|
||||
"f.req=" + Escape(WideStringToString(FormatString(LR"([[["MkEWBc","[[\"%s\",\"auto\",\"%s\",true],[null]]",null,"generic"]]])", JSON::Escape((JSON::Escape(text))), translateTo.Copy()))),
|
||||
L"Content-Type: application/x-www-form-urlencoded"
|
||||
})
|
||||
{
|
||||
// Response formatted as JSON: starts with "translatedText": " and translation is enclosed in quotes followed by a comma
|
||||
if (std::wsmatch results; std::regex_search(httpRequest.response, results, std::wregex(L"\"translatedText\": \"(.+?)\","))) return { true, results[1] };
|
||||
if (auto start = httpRequest.response.find(L"[["); start != std::string::npos)
|
||||
{
|
||||
if (auto blob = Copy(JSON::Parse(httpRequest.response.substr(start))[0][2].String())) if (auto translations = Copy(JSON::Parse(blob.value())[1][0].Array()))
|
||||
{
|
||||
std::wstring translation;
|
||||
if (translations->size() == 1 && (translations = Copy(translations.value()[0][5].Array())))
|
||||
{
|
||||
for (const auto& sentence : translations.value()) if (sentence[0].String()) (translation += *sentence[0].String()) += L" ";
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& conjugation : translations.value())
|
||||
if (auto sentence = conjugation[0].String()) if (auto gender = conjugation[2].String()) translation += FormatString(L"%s %s\n", *sentence, *gender);
|
||||
}
|
||||
if (!translation.empty()) return { true, translation };
|
||||
return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, blob.value()) };
|
||||
}
|
||||
}
|
||||
return { false, FormatString(L"%s: %s", TRANSLATION_ERROR, httpRequest.response) };
|
||||
}
|
||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||
|
||||
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): %s", TRANSLATION_ERROR, _InterlockedExchange(&TKK, 0), httpRequest.response) };
|
||||
}
|
||||
else return { false, FormatString(L"%s (code=%u)", TRANSLATION_ERROR, httpRequest.errorCode) };
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ public:
|
||||
Window()
|
||||
: QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
||||
{
|
||||
localize();
|
||||
connect(&loadButton, &QPushButton::clicked, this, &Window::LoadScript);
|
||||
|
||||
if (scriptEditor.toPlainText().isEmpty()) scriptEditor.setPlainText(LUA_INTRO);
|
||||
@ -83,9 +84,9 @@ private:
|
||||
|
||||
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||
{
|
||||
thread_local static struct { std::unique_ptr<lua_State, Functor<lua_close>> L{ luaL_newstate() }; operator lua_State*() { return L.get(); } } L;
|
||||
thread_local static auto _ = (luaL_openlibs(L), luaL_dostring(L, "function ProcessSentence() end"));
|
||||
thread_local static int revCount = 0;
|
||||
thread_local 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 int revCount = 0;
|
||||
|
||||
if (::revCount > revCount)
|
||||
{
|
||||
|
@ -55,44 +55,11 @@ std::wstring Escape(const std::wstring& text)
|
||||
return escaped;
|
||||
}
|
||||
|
||||
namespace JSON
|
||||
std::string Escape(const std::string& text)
|
||||
{
|
||||
void Unescape(std::wstring& text)
|
||||
{
|
||||
for (int i = 0; i < text.size(); ++i)
|
||||
{
|
||||
if (text[i] == L'\\')
|
||||
{
|
||||
text[i] = 0;
|
||||
if (text[i + 1] == L'r') text[i + 1] = 0; // 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;
|
||||
}
|
||||
}
|
||||
text.erase(std::remove(text.begin(), text.end(), 0), text.end());
|
||||
}
|
||||
|
||||
std::string Escape(const std::wstring& text)
|
||||
{
|
||||
std::string escaped = WideStringToString(text);
|
||||
int oldSize = escaped.size();
|
||||
escaped.resize(escaped.size() + std::count_if(escaped.begin(), escaped.end(), [](char ch) { return ch == '\n' || ch == '\r' || ch == '\t' || ch == '\\' || ch == '"'; }));
|
||||
auto out = escaped.rbegin();
|
||||
for (int i = oldSize - 1; i >= 0; --i)
|
||||
{
|
||||
if (escaped[i] == '\n') *out++ = 'n';
|
||||
else if (escaped[i] == '\t') *out++ = 't';
|
||||
else if (escaped[i] == '\r') *out++ = 'r';
|
||||
else if (escaped[i] == '\\' || escaped[i] == '"') *out++ = escaped[i];
|
||||
else
|
||||
{
|
||||
*out++ = escaped[i];
|
||||
continue;
|
||||
}
|
||||
*out++ = '\\';
|
||||
}
|
||||
escaped.erase(std::remove_if(escaped.begin(), escaped.end(), [](unsigned char ch) { return ch < 0x20; }), escaped.end());
|
||||
std::string escaped;
|
||||
for (unsigned char ch : text) escaped += FormatString("%%%02X", (int)ch);
|
||||
return escaped;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(assert(JSON::Parse<wchar_t>(LR"([{"string":"hello world","boolean":false,"number":1.67e+4,"null":null,"array":[]},"hello world"])")))
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <winhttp.h>
|
||||
#include <variant>
|
||||
|
||||
using InternetHandle = AutoHandle<Functor<WinHttpCloseHandle>>;
|
||||
|
||||
@ -29,9 +30,173 @@ struct HttpRequest
|
||||
};
|
||||
|
||||
std::wstring Escape(const std::wstring& text);
|
||||
std::string Escape(const std::string& text);
|
||||
|
||||
namespace JSON
|
||||
{
|
||||
void Unescape(std::wstring& text);
|
||||
std::string Escape(const std::wstring& text);
|
||||
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(), [](auto 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(int codepoint) { return { (wchar_t)codepoint }; } // TODO: surrogate pairs
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
struct Value : private std::variant<std::monostate, std::nullopt_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>
|
||||
{
|
||||
using std::variant<std::monostate, std::nullopt_t, bool, double, std::basic_string<C>, std::vector<Value<C>>, std::unordered_map<std::basic_string<C>, Value<C>>>::variant;
|
||||
|
||||
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
|
||||
{
|
||||
static const Value<C> failure;
|
||||
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
|
||||
{
|
||||
static const Value<C> failure;
|
||||
if (auto array = Array()) if (i < array->size()) return array->at(i);
|
||||
return 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[] = { text[i + 2], text[i + 3], text[i + 4], text[i + 5], 0 };
|
||||
unescaped += UTF<C>::FromCodepoint(strtol(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 {};
|
||||
|
||||
static 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), std::nullopt;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ public:
|
||||
Window()
|
||||
: QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
||||
{
|
||||
localize();
|
||||
ui.setupUi(this);
|
||||
|
||||
connect(ui.input, &QLineEdit::textEdited, this, &Window::setRegex);
|
||||
|
@ -17,6 +17,7 @@ public:
|
||||
Window()
|
||||
: QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
||||
{
|
||||
localize();
|
||||
connect(&linkButton, &QPushButton::clicked, this, &Window::Link);
|
||||
|
||||
layout.addWidget(&linkList);
|
||||
|
@ -26,10 +26,10 @@ extern int tokenCount, tokenRestoreDelay, maxSentenceSize;
|
||||
std::pair<bool, std::wstring> Translate(const std::wstring& text);
|
||||
|
||||
const char* LANGUAGE = u8"Language";
|
||||
const std::string TRANSLATION_CACHE_FILE = FormatString("%s Cache.txt", TRANSLATION_PROVIDER);
|
||||
const std::string TRANSLATION_CACHE_FILE = FormatString("%s Translation Cache.txt", TRANSLATION_PROVIDER);
|
||||
|
||||
QFormLayout* display;
|
||||
QSettings settings = openSettings();
|
||||
Settings settings;
|
||||
Synchronized<std::wstring> translateTo = L"en", apiKey;
|
||||
|
||||
Synchronized<std::map<std::wstring, std::wstring>> translationCache;
|
||||
@ -50,6 +50,7 @@ public:
|
||||
Window() :
|
||||
QDialog(nullptr, Qt::WindowMinMaxButtonsHint)
|
||||
{
|
||||
localize();
|
||||
display = new QFormLayout(this);
|
||||
|
||||
settings.beginGroup(TRANSLATION_PROVIDER);
|
||||
@ -159,9 +160,9 @@ bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo)
|
||||
if (cache) translationCache->try_emplace(sentence, translation);
|
||||
if (cache && translationCache->size() > savedSize + 50) SaveCache();
|
||||
|
||||
JSON::Unescape(translation);
|
||||
sentence += L"\n" + 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
|
||||
if (!translation.empty()) (sentence += L"\x200b \n") += translation;
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(assert(Translate(L"こんにちは").second.find(L"ello") != std::wstring::npos));
|
||||
TEST(assert(Translate(L"こんにちは").second.find(L"ello") != std::string::npos));
|
||||
|
@ -27,23 +27,15 @@ constexpr bool x64 = true;
|
||||
constexpr bool x64 = false;
|
||||
#endif
|
||||
|
||||
template <typename T, typename... Xs>
|
||||
struct ArrayImpl { using Type = std::tuple<T, Xs...>[]; };
|
||||
template <typename T>
|
||||
struct ArrayImpl<T> { using Type = T[]; };
|
||||
template <typename... Ts>
|
||||
using Array = typename ArrayImpl<Ts...>::Type;
|
||||
template <typename T, typename... Xs> struct ArrayImpl { using Type = std::tuple<T, Xs...>[]; };
|
||||
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>;
|
||||
|
||||
template <typename V>
|
||||
struct Identity { V operator()(V v) const { return v; } };
|
||||
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
|
||||
|
||||
struct PermissivePointer
|
||||
{
|
||||
template <typename T>
|
||||
operator T*() { return (T*)p; }
|
||||
template <typename T> operator T*() { return (T*)p; }
|
||||
void* p;
|
||||
};
|
||||
|
||||
@ -77,29 +69,23 @@ public:
|
||||
|
||||
Locker Acquire() { return { std::unique_lock(m), contents }; }
|
||||
Locker operator->() { return Acquire(); }
|
||||
|
||||
T Copy()
|
||||
{
|
||||
return Acquire().contents;
|
||||
}
|
||||
T Copy() { return Acquire().contents; }
|
||||
|
||||
private:
|
||||
T contents;
|
||||
M m;
|
||||
};
|
||||
|
||||
static struct
|
||||
static struct // should be inline but MSVC (linker) is bugged
|
||||
{
|
||||
BYTE DUMMY[100];
|
||||
template <typename T>
|
||||
operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
||||
inline static BYTE DUMMY[100];
|
||||
template <typename T> operator T*() { static_assert(sizeof(T) < sizeof(DUMMY)); return (T*)DUMMY; }
|
||||
} DUMMY;
|
||||
|
||||
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(disable: 4996)
|
||||
@ -148,6 +134,8 @@ inline void TEXTRACTOR_MESSAGE(const wchar_t* format, const Args&... args) { Mes
|
||||
template <typename... Args>
|
||||
inline void TEXTRACTOR_DEBUG(const wchar_t* format, const Args&... args) { std::thread([=] { TEXTRACTOR_MESSAGE(format, args...); }).detach(); }
|
||||
|
||||
void localize();
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define TEST(...) static auto _ = CreateThread(nullptr, 0, [](auto) { __VA_ARGS__; return 0UL; }, NULL, 0, nullptr);
|
||||
#else
|
||||
|
@ -23,8 +23,8 @@ static thread_local bool ok;
|
||||
|
||||
constexpr auto CONFIG_FILE = u8"Textractor.ini";
|
||||
constexpr auto WINDOW = u8"Window";
|
||||
inline QSettings openSettings(QObject* parent = nullptr) { return { CONFIG_FILE, QSettings::IniFormat, parent }; }
|
||||
|
||||
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); } };
|
||||
inline std::wstring S(const QString& s) { return { s.toStdWString() }; }
|
||||
inline QString S(const std::string& s) { return QString::fromStdString(s); }
|
||||
|
38
text.cpp
38
text.cpp
@ -215,7 +215,7 @@ const char* THREAD_LINK_FROM = u8"Thread number to link from";
|
||||
const char* THREAD_LINK_TO = u8"Thread number to link to";
|
||||
const char* HEXADECIMAL = u8"Hexadecimal";
|
||||
|
||||
static auto _ = []
|
||||
void localize()
|
||||
{
|
||||
#ifdef TURKISH
|
||||
NATIVE_LANGUAGE = "Turkish";
|
||||
@ -1158,26 +1158,26 @@ original_text의 빈공간은 무시되지만, replacement_text는 공백과 엔
|
||||
ATTACH_INFO = u8R"(Si vous ne voyez pas le processus que vous souhaitez joindre, essayez de l'exécuter avec les droits d'administrateur
|
||||
Vous pouvez également saisir l'ID de processus)";
|
||||
SELECT_PROCESS_INFO = u8"Si vous saisissez manuellement le nom du fichier de processus, veuillez utiliser le chemin exact";
|
||||
FROM_COMPUTER = u8"Selectionner depuis l'ordinateur";
|
||||
FROM_COMPUTER = u8"Sélectionner depuis l'ordinateur";
|
||||
PROCESSES = u8"Processus (*.exe)";
|
||||
CODE_INFODUMP = u8R"(Enter read code
|
||||
CODE_INFODUMP = u8R"(Entrez le read code
|
||||
R{S|Q|V|M}[null_length<][codepage#]@addr
|
||||
OR
|
||||
Enter hook code
|
||||
OU
|
||||
Entrez le hook code
|
||||
H{A|B|W|H|S|Q|V|M}[F][null_length<][N][codepage#][padding+]data_offset[*deref_offset][:split_offset[*deref_offset]]@addr[:module[:func]]
|
||||
All numbers except codepage/null_length in hexadecimal
|
||||
Default codepage is 932 (Shift-JIS) but this can be changed in settings
|
||||
Tous les nombres sauf codepage/null_length sont en hexadécimal
|
||||
Le codepage par défaut est 932 (Shift-JIS) mais cela peut être modifié dans les paramètres
|
||||
A/B: codepage char little/big endian
|
||||
W: UTF-16 char
|
||||
H: Two hex bytes
|
||||
S/Q/V/M: codepage/UTF-16/UTF-8/hex string
|
||||
F: treat strings as full lines of text
|
||||
N: don't use context
|
||||
N: n'utilise pas de contexte
|
||||
null_length: length of null terminator used for string
|
||||
padding: length of padding data before string (C struct { int64_t size; char string[500]; } needs padding = 8)
|
||||
Negatives for data_offset/split_offset refer to registers
|
||||
-4 for EAX, -8 for ECX, -C for EDX, -10 for EBX, -14 for ESP, -18 for EBP, -1C for ESI, -20 for EDI
|
||||
-C for RAX, -14 for RBX, -1C for RCX, -24 for RDX, and so on for RSP, RBP, RSI, RDI, R8-R15
|
||||
Les valeures négatives pour data_offset/split_offset font références aux registres
|
||||
-4 pour EAX, -8 pour ECX, -C pour EDX, -10 pour EBX, -14 pour ESP, -18 pour EBP, -1C pour ESI, -20 pour EDI
|
||||
-C pour RAX, -14 pour RBX, -1C pour RCX, -24 pour RDX, and so on for RSP, RBP, RSI, RDI, R8-R15
|
||||
* means dereference pointer+deref_offset)";
|
||||
SAVE_SETTINGS = u8"Sauvergarder les paramètres";
|
||||
EXTEN_WINDOW_INSTRUCTIONS = u8R"(Pour ajouter une extension, cliquez avec le bouton droit sur la liste des extensions
|
||||
@ -1192,7 +1192,7 @@ Pour supprimer une extension, sélectionnez-la et appuyez sur supprimer)";
|
||||
USE_JP_LOCALE = u8"Émuler les paramètres régionaux japonais?";
|
||||
FAILED_TO_CREATE_CONFIG_FILE = u8"Impossible de créer le fichier de configuration \"%1\"";
|
||||
HOOK_SEARCH_UNSTABLE_WARNING = u8"La recherche de crochets est instable! Soyez prêt à ce que votre jeu plante!";
|
||||
SEARCH_CJK = u8"Recher pour Chinois/Japonais/Coréen";
|
||||
SEARCH_CJK = u8"Rechercher pour Chinois/Japonais/Coréen";
|
||||
SEARCH_PATTERN = u8"Modèle de recherche (tableau d'octets hexadécimaux)";
|
||||
SEARCH_DURATION = u8"Durée de la recherche (ms)";
|
||||
SEARCH_MODULE = u8"Recherche sans module";
|
||||
@ -1201,14 +1201,14 @@ Pour supprimer une extension, sélectionnez-la et appuyez sur supprimer)";
|
||||
MIN_ADDRESS = u8"Minimum d'adresses (hex)";
|
||||
MAX_ADDRESS = u8"Maximum d'adresses (hex)";
|
||||
STRING_OFFSET = u8"Décalage de la chaîne (hex)";
|
||||
HOOK_SEARCH_FILTER = u8"Results must match this regex";
|
||||
HOOK_SEARCH_FILTER = u8"Les résultats doivent correspondre à ce regex";
|
||||
TEXT = u8"Texte";
|
||||
CODEPAGE = u8"Code de page";
|
||||
SEARCH_FOR_TEXT = u8"Rechercher un texte spécifique";
|
||||
START_HOOK_SEARCH = u8"Lancer la recherche de hook";
|
||||
SAVE_SEARCH_RESULTS = u8"Sauvergarder les résultats de la recherche";
|
||||
TEXT_FILES = u8"Texte (*.txt)";
|
||||
DOUBLE_CLICK_TO_REMOVE_HOOK = u8"Double click un hook pour l'enlever";
|
||||
DOUBLE_CLICK_TO_REMOVE_HOOK = u8"Double cliquer sur un hook pour l'enlever";
|
||||
FILTER_REPETITION = u8"Répétition de filtre";
|
||||
AUTO_ATTACH = u8"Attachement Automatique";
|
||||
ATTACH_SAVED_ONLY = u8"Attachement Automatique (Sauvergardé seulement)";
|
||||
@ -1293,8 +1293,8 @@ Ce fichier doit être encodé en UTF-8.)";
|
||||
Fonctionne uniquement si cette extension est utilisée directement après une extension de traduction)";
|
||||
SIZE_LOCK = u8"Verouiller la taille";
|
||||
OPACITY = u8"Opacité";
|
||||
BG_COLOR = u8"COuleur d'arrière-plan";
|
||||
TEXT_COLOR = u8"COuleur du texte";
|
||||
BG_COLOR = u8"Couleur d'arrière-plan";
|
||||
TEXT_COLOR = u8"Couleur du texte";
|
||||
TEXT_OUTLINE = u8"Contour du texte";
|
||||
OUTLINE_COLOR = u8"Couleur du contour";
|
||||
OUTLINE_SIZE = u8"Taille du contour";
|
||||
@ -1332,8 +1332,8 @@ Ce fichier doit être encodé en Unicode (UTF-16 Little Endian).)";
|
||||
LINK = u8"Lien";
|
||||
THREAD_LINK_FROM = u8"Nombre du thread du lien depuis";
|
||||
THREAD_LINK_TO = u8"Nombre du thread du lien a";
|
||||
HEXADECIMAL = u8"Hexadecimal";
|
||||
HEXADECIMAL = u8"Hexadécimal";
|
||||
#endif // FRENCH
|
||||
};
|
||||
|
||||
return 0;
|
||||
}();
|
||||
static auto _ = (localize(), 0);
|
||||
|
@ -16396,21 +16396,21 @@ bool InsertShinyDaysGameHook()
|
||||
0xff,0x83,0x70,0x03,0x00,0x00,0x33,0xf6,
|
||||
0xc6,0x84,0x24,0x90,0x02,0x00,0x00,0x02
|
||||
};
|
||||
LPVOID addr = (LPVOID)0x42ad94;
|
||||
if (::memcmp(addr, bytes, sizeof(bytes)) != 0) {
|
||||
ConsoleOutput("vnreng:ShinyDays: only work for 1.00");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto addr : Util::SearchMemory(bytes, sizeof(bytes))) {
|
||||
HookParam hp = {};
|
||||
hp.address = 0x42ad9c;
|
||||
hp.address = addr + 0x8;
|
||||
hp.text_fun = SpecialGameHookShinyDays;
|
||||
hp.type = USING_UNICODE | USING_STRING | NO_CONTEXT;
|
||||
ConsoleOutput("vnreng: INSERT ShinyDays");
|
||||
NewHook(hp, "ShinyDays 1.00");
|
||||
ConsoleOutput("Textractor: INSERT ShinyDays");
|
||||
NewHook(hp, "ShinyDays");
|
||||
return true;
|
||||
}
|
||||
|
||||
ConsoleOutput("Textractor:ShinyDays: pattern not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
#if 0 // disabled as lova does not allow module from being modified
|
||||
/** 7/19/2015: Game engine specific for http://lova.jp
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user