mirror of
https://git.chinosk6.cn/chinosk/gkms-local.git
synced 2026-10-07 07:01:46 +07:00
Add Built-in Patch Support (#3)
This commit is contained in:
1 parent
0e1ad6959b
commit
adb1a687b8
70 files changed
+19701
-45
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user