Add Built-in Patch Support (#3)

This commit is contained in:
chinosk authored and GitHub committed 2024-08-07 00:27:40 +08:00
1 parent 0e1ad6959b
commit adb1a687b8
70 files changed
+19701 -45

No files matched your search

+12
View File
@@ -333,6 +333,13 @@ namespace GakumasLocal::HookMain {
"TMPro", "TMP_Text", "get_font");
static auto set_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
"TMPro", "TMP_Text", "set_font");
// static auto set_fontMaterial = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
// "TMPro", "TMP_Text", "set_fontMaterial");
// static auto ForceMeshUpdate = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
// "TMPro", "TMP_Text", "ForceMeshUpdate");
//
// static auto get_material = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
// "TMPro", "TMP_Asset", "get_material");
static auto set_sourceFontFile = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
"TMP_FontAsset", "set_sourceFontFile");
@@ -351,6 +358,10 @@ namespace GakumasLocal::HookMain {
if (updatedFontPtrs.size() > 200) updatedFontPtrs.clear();
}
set_font->Invoke<void>(TMP_Textself, fontAsset);
// auto fontMaterial = get_material->Invoke<void*>(fontAsset);
// set_fontMaterial->Invoke<void>(TMP_Textself, fontMaterial);
// ForceMeshUpdate->Invoke<void>(TMP_Textself, false, false);
}
DEFINE_HOOK(void, TMP_Text_PopulateTextBackingArray, (void* self, UnityResolve::UnityType::String* text, int start, int length)) {
@@ -365,6 +376,7 @@ namespace GakumasLocal::HookMain {
std::string transText;
if (Local::GetGenericText(origText, &transText)) {
const auto newText = UnityResolve::UnityType::String::New(transText);
UpdateFont(self);
return TMP_Text_PopulateTextBackingArray_Orig(self, newText, 0, newText->length);
}
+56 -5
View File
@@ -15,6 +15,7 @@
#include <cctype>
#include <algorithm>
#include "BaseDefine.h"
#include "string_parser/StringParser.hpp"
namespace GakumasLocal::Local {
@@ -22,9 +23,12 @@ namespace GakumasLocal::Local {
std::unordered_map<std::string, std::string> i18nDumpData{};
std::unordered_map<std::string, std::string> genericText{};
std::unordered_map<std::string, std::string> genericSplitText{};
std::unordered_map<std::string, std::string> genericFmtText{};
std::vector<std::string> genericTextDumpData{};
std::vector<std::string> genericSplittedDumpData{};
std::vector<std::string> genericOrigTextDumpData{};
std::vector<std::string> genericFmtTextDumpData{};
std::unordered_set<std::string> translatedText{};
int genericDumpFileIndex = 0;
const std::string splitTextPrefix = "[__split__]";
@@ -64,7 +68,8 @@ namespace GakumasLocal::Local {
enum class DumpStrStat {
DEFAULT = 0,
SPLITTABLE_ORIG = 1,
SPLITTED = 2
SPLITTED = 2,
FMT = 3
};
enum class SplitTagsTranslationStat {
@@ -262,6 +267,15 @@ namespace GakumasLocal::Local {
return ret;
}
void ReplaceNumberComma(std::string* orig) {
if (!orig->contains(",")) return;
std::string newStr = *orig;
ReplaceString(&newStr, ",", ",");
if (IsPureStringValue(newStr)) {
*orig = newStr;
}
}
SplitTagsTranslationStat GetSplitTagsTranslationFull(const std::string& origTextIn, std::string* newText, std::vector<std::string>& unTransResultRet) {
// static const std::u16string splitFlags = u"0123456789++--%%【】.";
static const std::unordered_set<char16_t> splitFlags = {u'0', u'1', u'2', u'3', u'4', u'5',
@@ -306,7 +320,9 @@ namespace GakumasLocal::Local {
return SplitTagsTranslationStat::NO_SPLIT_AND_EMPTY;
}
else {
return SplitTagsTranslationStat::NO_SPLIT;
if (!(!origText.empty() && splitFlags.contains(origText[0]))) { // 开头为特殊符号或数字
return SplitTagsTranslationStat::NO_SPLIT;
}
}
}
checkCurrentWaitingReplaceTextAndClear()
@@ -317,8 +333,9 @@ namespace GakumasLocal::Local {
bool hasNotTrans = false;
if (!waitingReplaceTexts.empty()) {
for (const auto& i : waitingReplaceTexts) {
const auto searchResult = findInMapIgnoreSpace(i, genericSplitText);
std::string searchResult = findInMapIgnoreSpace(i, genericSplitText);
if (!searchResult.empty()) {
ReplaceNumberComma(&searchResult);
ReplaceString(newText, i, searchResult);
hasTrans = true;
}
@@ -358,6 +375,7 @@ namespace GakumasLocal::Local {
LoadJsonDataToMap(genericFile, genericText, true, true, true);
genericSplitText.clear();
genericFmtText.clear();
LoadJsonDataToMap(genericSplitFile, genericSplitText, true, true, true);
if (std::filesystem::exists(genericDir) || std::filesystem::is_directory(genericDir)) {
for (const auto& entry : std::filesystem::recursive_directory_iterator(genericDir)) {
@@ -367,6 +385,9 @@ namespace GakumasLocal::Local {
if (currFile.filename().string().ends_with(".split.json")) { // split text file
LoadJsonDataToMap(currFile, genericSplitText, true, false, true);
}
if (currFile.filename().string().ends_with(".fmt.json")) { // fmt text file
LoadJsonDataToMap(currFile, genericFmtText, true, false, false);
}
else {
LoadJsonDataToMap(currFile, genericText, true, false, true);
}
@@ -442,6 +463,10 @@ namespace GakumasLocal::Local {
if (genericDumpFileIndex == 0) return "generic_orig.json";
return Log::StringFormat("generic_orig_%d.json", genericDumpFileIndex);
}
else if (stat == DumpStrStat::FMT) {
if (genericDumpFileIndex == 0) return "generic.fmt.json";
return Log::StringFormat("generic_%d.fmt.json", genericDumpFileIndex);
}
else {
if (genericDumpFileIndex == 0) return "generic.json";
return Log::StringFormat("generic_%d.json", genericDumpFileIndex);
@@ -452,10 +477,11 @@ namespace GakumasLocal::Local {
void DumpGenericText(const std::string& origText, DumpStrStat stat = DumpStrStat::DEFAULT) {
if (translatedText.contains(origText)) return;
std::array<std::reference_wrapper<std::vector<std::string>>, 3> targets = {
std::array<std::reference_wrapper<std::vector<std::string>>, 4> targets = {
genericTextDumpData,
genericOrigTextDumpData,
genericSplittedDumpData
genericSplittedDumpData,
genericFmtTextDumpData
};
auto& appendTarget = targets[static_cast<int>(stat)].get();
@@ -475,25 +501,50 @@ namespace GakumasLocal::Local {
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::DEFAULT), genericTextDumpData);
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTABLE_ORIG), genericOrigTextDumpData);
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::SPLITTED), genericSplittedDumpData, splitTextPrefix);
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(DumpStrStat::FMT), genericFmtTextDumpData);
genericTextDumpData.clear();
genericSplittedDumpData.clear();
genericOrigTextDumpData.clear();
genericFmtTextDumpData.clear();
inDumpGeneric = false;
}).detach();
}
bool GetGenericText(const std::string& origText, std::string* newStr) {
// 完全匹配
if (const auto iter = genericText.find(origText); iter != genericText.end()) {
*newStr = iter->second;
return true;
}
// 不翻译翻译过的文本
if (translatedText.contains(origText)) {
return false;
}
// fmt 文本
auto fmtText = StringParser::ParseItems::parse(origText, false);
if (fmtText.isValid) {
const auto fmtStr = fmtText.ToFmtString();
if (auto it = genericFmtText.find(fmtStr); it != genericFmtText.end()) {
auto newRet = fmtText.MergeText(it->second);
if (!newRet.empty()) {
*newStr = newRet;
return true;
}
}
if (Config::dumpText) {
DumpGenericText(fmtStr, DumpStrStat::FMT);
}
}
auto ret = false;
// 分割匹配
std::vector<std::string> unTransResultRet;
const auto splitTransStat = GetSplitTagsTranslationFull(origText, newStr, unTransResultRet);
switch (splitTransStat) {
case SplitTagsTranslationStat::FULL_TRANS: {
DumpGenericText(origText, DumpStrStat::SPLITTABLE_ORIG);
return true;
} break;
+76 -1
View File
@@ -3,6 +3,7 @@
#include <codecvt>
#include <locale>
#include <jni.h>
#include "fmt/core.h"
extern JavaVM* g_javaVM;
@@ -88,11 +89,85 @@ namespace GakumasLocal::Misc {
int CSEnum::GetValueByName(const std::string &name) {
for (int i = 0; i < names.size(); i++) {
if (names[i].compare(name) == 0) {
if (names[i] == name) {
return values[i];
}
}
return values[0];
}
namespace StringFormat {
template<typename... Args>
std::string string_format(const std::string& fmt, Args&&... args) {
// return std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
return fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...);
}
template <std::size_t N, std::size_t... Indices, typename T>
auto vectorToTupleImpl(const std::vector<T>& vec, std::index_sequence<Indices...>) {
if (vec.size() != N) {
// printf("vec.size: %zu, N: %zu\n", vec.size(), N);
throw std::out_of_range("Vector size does not match tuple size.");
}
return std::make_tuple(vec[Indices]...);
}
template <std::size_t N, typename T>
auto vectorToTuple(const std::vector<T>& vec) {
return vectorToTupleImpl<N>(vec, std::make_index_sequence<N>{});
}
template <typename T>
std::string stringFormat(const std::string& fmt, const std::vector<T>& vec) {
std::string ret = fmt;
#define CASE_ARG_COUNT(N) \
case N: {\
auto tp = vectorToTuple<N>(vec); \
std::apply([&](auto&&... args) { \
ret = string_format(fmt, args...); \
}, tp); } break;
switch (vec.size()) {
CASE_ARG_COUNT(1)
CASE_ARG_COUNT(2)
CASE_ARG_COUNT(3)
CASE_ARG_COUNT(4)
CASE_ARG_COUNT(5)
CASE_ARG_COUNT(6)
CASE_ARG_COUNT(7)
CASE_ARG_COUNT(8)
CASE_ARG_COUNT(9)
CASE_ARG_COUNT(10)
CASE_ARG_COUNT(11)
CASE_ARG_COUNT(12)
CASE_ARG_COUNT(13)
CASE_ARG_COUNT(14)
CASE_ARG_COUNT(15)
CASE_ARG_COUNT(16)
CASE_ARG_COUNT(17)
CASE_ARG_COUNT(18)
CASE_ARG_COUNT(19)
CASE_ARG_COUNT(20)
CASE_ARG_COUNT(21)
CASE_ARG_COUNT(22)
CASE_ARG_COUNT(23)
CASE_ARG_COUNT(24)
}
return ret;
}
std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec) {
try {
return stringFormat(fmt, vec);
}
catch (std::exception& e) {
return fmt;
}
}
}
}
@@ -73,5 +73,9 @@ namespace GakumasLocal {
size_t maxSize;
T sum;
};
namespace StringFormat {
std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec);
}
}
}
@@ -0,0 +1,123 @@
#include <unordered_set>
#include "StringParser.hpp"
#include "fmt/core.h"
#include "fmt/ranges.h"
#include "../Misc.hpp"
namespace StringParser {
std::string ParseItems::ToFmtString() {
std::vector<std::string> ret{};
int currFlagIndex = 0;
for (const auto& i : items) {
if (i.type == ParseItemType::FLAG) {
ret.push_back(fmt::format("{{{}}}", currFlagIndex));
currFlagIndex++;
}
else {
ret.push_back(i.content);
}
}
return fmt::format("{}", fmt::join(ret, ""));
}
std::vector<std::string> ParseItems::GetFlagValues() {
std::vector<std::string> ret{};
for (const auto& i : items) {
if (i.type == ParseItemType::FLAG) {
ret.push_back(i.content);
}
}
return ret;
}
ParseItems ParseItems::parse(const std::string &str, bool parseTags) {
static const std::unordered_set<char16_t> splitFlags = {u'0', u'1', u'2', u'3', u'4', u'5',
u'6', u'7', u'8', u'9', u'+', u'+',
u'-', u'-', u'%', u'%',
u'.', u'×', u',', u','};
ParseItems result;
if (str.contains("{")) {
result.isValid = false;
return result;
}
std::u16string origText = GakumasLocal::Misc::ToUTF16(str);
bool isInTag = false;
bool isInFlagSequence = false;
std::u16string currentCacheText;
for (char16_t currChar : origText) {
if (parseTags && currChar == u'<') {
if (!currentCacheText.empty()) {
result.items.push_back({isInFlagSequence ? ParseItemType::FLAG : ParseItemType::TEXT,
GakumasLocal::Misc::ToUTF8(currentCacheText)});
currentCacheText.clear();
isInFlagSequence = false;
}
isInTag = true;
currentCacheText.push_back(currChar);
} else if (parseTags && currChar == u'>') {
isInTag = false;
currentCacheText.push_back(currChar);
result.items.push_back({ParseItemType::FLAG, GakumasLocal::Misc::ToUTF8(currentCacheText)});
currentCacheText.clear();
} else if (isInTag) {
currentCacheText.push_back(currChar);
} else if (splitFlags.contains(currChar)) {
if (!isInFlagSequence && !currentCacheText.empty()) {
result.items.push_back({ParseItemType::TEXT, GakumasLocal::Misc::ToUTF8(currentCacheText)});
currentCacheText.clear();
}
isInFlagSequence = true;
currentCacheText.push_back(currChar);
} else {
if (isInFlagSequence && !currentCacheText.empty()) {
result.items.push_back({ParseItemType::FLAG, GakumasLocal::Misc::ToUTF8(currentCacheText)});
currentCacheText.clear();
isInFlagSequence = false;
}
currentCacheText.push_back(currChar);
}
}
if (!currentCacheText.empty()) {
result.items.push_back({isInFlagSequence ? ParseItemType::FLAG : ParseItemType::TEXT,
GakumasLocal::Misc::ToUTF8(currentCacheText)});
}
for (auto& i : result.items) {
if (i.type == ParseItemType::FLAG) {
return result;
}
}
result.isValid = false;
return result;
}
std::string ParseItems::MergeText(ParseItems &textTarget, ParseItems &valueTarget) {
if (!textTarget.isValid) return "";
if (!valueTarget.isValid) return "";
const auto fmtText = textTarget.ToFmtString();
const auto values = valueTarget.GetFlagValues();
const std::string ret = GakumasLocal::Misc::StringFormat::stringFormatString(fmtText, values);
return {ret.begin(), ret.end()};
}
std::string ParseItems::MergeText(const std::string &newStr) {
if (!isValid) return "";
const auto values = GetFlagValues();
return GakumasLocal::Misc::StringFormat::stringFormatString(newStr, values);
}
int ParseItems::GetFlagCount() {
int ret = 0;
for (auto& i : items) {
if (i.type == ParseItemType::FLAG) {
ret++;
}
}
return ret;
}
}
@@ -0,0 +1,30 @@
#pragma once
#include <string>
#include <vector>
namespace StringParser {
enum class ParseItemType {
FLAG,
TEXT
};
struct ParseItem {
ParseItemType type;
std::string content;
};
struct ParseItems {
bool isValid = true;
std::vector<ParseItem> items;
std::string ToFmtString();
std::vector<std::string> GetFlagValues();
std::string MergeText(const std::string& newStr);
int GetFlagCount();
static ParseItems parse(const std::string& str, bool parseTags);
static std::string MergeText(ParseItems& textTarget, ParseItems& valueTarget);
};
}