mirror of
https://git.chinosk6.cn/chinosk/gkms-local.git
synced 2026-10-07 07:01:46 +07:00
Feature dump more text (#16)
* 修复 Dump 重复判断出错的问题 * 支持 dump 更多 generic 文本 * 支持加载多个 generic json 文件, 增加强制导出资源, 构建排除 `raw` 文件夹 * add version display * update build.yml * update deps * generic 文本支持去标签匹配 * Generic dump 只 dump 分割后的文本 * add sign * update event dispatcher * 增加 generic dump 开关、dump 文件编号调整 * update config.hpp * Add `BaseDefine.h`
This commit is contained in:
1 parent
37e98f61a0
commit
75dda059f1
69 files changed
+20064
-19389
No files matched your search
@@ -0,0 +1,26 @@
|
||||
#define KEY_W 51
|
||||
#define KEY_S 47
|
||||
#define KEY_A 29
|
||||
#define KEY_D 32
|
||||
#define KEY_R 46
|
||||
#define KEY_Q 45
|
||||
#define KEY_E 33
|
||||
#define KEY_F 34
|
||||
#define KEY_I 37
|
||||
#define KEY_K 39
|
||||
#define KEY_J 38
|
||||
#define KEY_L 40
|
||||
#define KEY_V 50
|
||||
#define KEY_UP 19
|
||||
#define KEY_DOWN 20
|
||||
#define KEY_LEFT 21
|
||||
#define KEY_RIGHT 22
|
||||
#define KEY_CTRL 113
|
||||
#define KEY_SHIFT 59
|
||||
#define KEY_ALT 57
|
||||
#define KEY_SPACE 62
|
||||
#define KEY_ADD 70
|
||||
#define KEY_SUB 69
|
||||
|
||||
#define WM_KEYDOWN 0
|
||||
#define WM_KEYUP 1
|
||||
@@ -336,24 +336,27 @@ namespace GakumasLocal::HookMain {
|
||||
set_font->Invoke<void>(TMP_Text_this, fontAsset);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, TMP_Text_set_text, (void* _this, UnityResolve::UnityType::String* text)) {
|
||||
// Log::DebugFmt("TMP_Text_set_text: %s", text->ToString().c_str());
|
||||
DEFINE_HOOK(void, TMP_Text_PopulateTextBackingArray, (void* _this, UnityResolve::UnityType::String* text, int start, int length)) {
|
||||
if (!text) {
|
||||
return TMP_Text_PopulateTextBackingArray_Orig(_this, text, start, length);
|
||||
}
|
||||
|
||||
static auto Substring = Il2cppUtils::GetMethod("mscorlib.dll", "System", "String", "Substring",
|
||||
{"System.Int32", "System.Int32"});
|
||||
|
||||
const std::string origText = Substring->Invoke<Il2cppString*>(text, start, length)->ToString();
|
||||
std::string transText;
|
||||
if (Local::GetGenericText(text->ToString(), &transText)) {
|
||||
return TMP_Text_set_text_Orig(_this, UnityResolve::UnityType::String::New(transText));
|
||||
if (Local::GetGenericText(origText, &transText)) {
|
||||
const auto newText = UnityResolve::UnityType::String::New(transText);
|
||||
return TMP_Text_PopulateTextBackingArray_Orig(_this, newText, 0, newText->length);
|
||||
}
|
||||
|
||||
if (Config::textTest) {
|
||||
TMP_Text_set_text_Orig(_this, UnityResolve::UnityType::String::New("[TS]" + text->ToString()));
|
||||
TMP_Text_PopulateTextBackingArray_Orig(_this, UnityResolve::UnityType::String::New("[TP]" + text->ToString()), start, length + 4);
|
||||
}
|
||||
else {
|
||||
TMP_Text_set_text_Orig(_this, text);
|
||||
TMP_Text_PopulateTextBackingArray_Orig(_this, text, start, length);
|
||||
}
|
||||
|
||||
static auto set_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
|
||||
"TMPro", "TMP_Text", "set_font");
|
||||
//auto font = GetReplaceFontAsset();
|
||||
//set_font->Invoke<void>(_this, font);
|
||||
UpdateFont(_this);
|
||||
}
|
||||
|
||||
@@ -370,10 +373,10 @@ namespace GakumasLocal::HookMain {
|
||||
std::string transText;
|
||||
if (Local::GetGenericText(currText->ToString(), &transText)) {
|
||||
if (Config::textTest) {
|
||||
TMP_Text_set_text_Orig(_this, UnityResolve::UnityType::String::New("[TA]" + transText));
|
||||
set_Text_method->Invoke<void>(_this, UnityResolve::UnityType::String::New("[TA]" + transText));
|
||||
}
|
||||
else {
|
||||
TMP_Text_set_text_Orig(_this, UnityResolve::UnityType::String::New(transText));
|
||||
set_Text_method->Invoke<void>(_this, UnityResolve::UnityType::String::New(transText));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -721,8 +724,9 @@ namespace GakumasLocal::HookMain {
|
||||
ADD_HOOK(TextMeshProUGUI_Awake, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TextMeshProUGUI", "Awake"));
|
||||
|
||||
ADD_HOOK(TMP_Text_set_text, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_Text", "set_text"));
|
||||
ADD_HOOK(TMP_Text_PopulateTextBackingArray, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_Text", "PopulateTextBackingArray",
|
||||
{"System.String", "System.Int32", "System.Int32"}));
|
||||
|
||||
ADD_HOOK(TextField_set_value, Il2cppUtils::GetMethodPointer("UnityEngine.UIElementsModule.dll", "UnityEngine.UIElements",
|
||||
"TextField", "set_value"));
|
||||
@@ -777,13 +781,12 @@ namespace GakumasLocal::HookMain {
|
||||
ADD_HOOK(CampusActorController_LateUpdate,
|
||||
Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common",
|
||||
"CampusActorController", "LateUpdate"));
|
||||
|
||||
/*
|
||||
static auto CampusActorController_klass = Il2cppUtils::GetClass("campus-submodule.Runtime.dll",
|
||||
"Campus.Common", "CampusActorController");
|
||||
for (const auto& i : CampusActorController_klass->methods) {
|
||||
Log::DebugFmt("CampusActorController.%s at %p", i->name.c_str(), i->function);
|
||||
}
|
||||
|
||||
}*/
|
||||
|
||||
ADD_HOOK(CampusQualityManager_set_TargetFrameRate,
|
||||
Il2cppUtils::GetMethodPointer("campus-submodule.Runtime.dll", "Campus.Common",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Local.h"
|
||||
#include "Log.h"
|
||||
#include "Plugin.h"
|
||||
#include "config/Config.hpp"
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
@@ -8,6 +9,9 @@
|
||||
#include <unordered_set>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <thread>
|
||||
#include <regex>
|
||||
#include <ranges>
|
||||
#include "BaseDefine.h"
|
||||
|
||||
|
||||
namespace GakumasLocal::Local {
|
||||
@@ -16,16 +20,19 @@ namespace GakumasLocal::Local {
|
||||
std::unordered_map<std::string, std::string> genericText{};
|
||||
std::vector<std::string> genericTextDumpData{};
|
||||
std::unordered_set<std::string> translatedText{};
|
||||
int genericDumpFileIndex = 0;
|
||||
|
||||
std::filesystem::path GetBasePath() {
|
||||
return Plugin::GetInstance().GetHookInstaller()->localizationFilesDir;
|
||||
}
|
||||
|
||||
void LoadJsonDataToMap(const std::filesystem::path& filePath, std::unordered_map<std::string, std::string>& dict,
|
||||
const bool insertToTranslated = false) {
|
||||
const bool insertToTranslated = false, const bool needClearDict = true) {
|
||||
if (!exists(filePath)) return;
|
||||
try {
|
||||
dict.clear();
|
||||
if (needClearDict) {
|
||||
dict.clear();
|
||||
}
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
Log::ErrorFmt("Load %s failed.\n", filePath.c_str());
|
||||
@@ -106,15 +113,113 @@ namespace GakumasLocal::Local {
|
||||
}
|
||||
}
|
||||
|
||||
std::string to_lower(const std::string& str) {
|
||||
std::string lower_str = str;
|
||||
std::transform(lower_str.begin(), lower_str.end(), lower_str.begin(), ::tolower);
|
||||
return lower_str;
|
||||
}
|
||||
|
||||
bool IsPureStringValue(const std::string& str) {
|
||||
static std::unordered_set<char> notDeeds = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':',
|
||||
'/', ' ', '.', '%', ',', '+', '-', 'x', '\n'};
|
||||
for (const auto& i : str) {
|
||||
if (!notDeeds.contains(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitByTags(const std::string& origText) {
|
||||
static const std::regex tagsRe("<.*?>(.*?)</.*?>");
|
||||
std::string text = origText;
|
||||
std::smatch match;
|
||||
|
||||
std::vector<std::string> ret{};
|
||||
|
||||
std::string lastSuffix;
|
||||
while (std::regex_search(text, match, tagsRe)) {
|
||||
const auto tagValue = match[1].str();
|
||||
if (IsPureStringValue(tagValue)) {
|
||||
ret.push_back(match.prefix().str());
|
||||
lastSuffix = match.suffix().str();
|
||||
}
|
||||
text = match.suffix().str();
|
||||
}
|
||||
if (!lastSuffix.empty()) {
|
||||
ret.push_back(lastSuffix);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ProcessGenericTextLabels() {
|
||||
std::unordered_map<std::string, std::string> appendsText{};
|
||||
|
||||
for (const auto& i : genericText) {
|
||||
const auto origContents = SplitByTags(i.first);
|
||||
if (origContents.empty()) {
|
||||
continue;
|
||||
}
|
||||
const auto translatedContents = SplitByTags(i.second);
|
||||
if (origContents.size() == translatedContents.size()) {
|
||||
for (const auto& [orig, trans] : std::ranges::views::zip(origContents, translatedContents)) {
|
||||
appendsText.emplace(orig, trans);
|
||||
}
|
||||
}
|
||||
}
|
||||
genericText.insert(appendsText.begin(), appendsText.end());
|
||||
}
|
||||
|
||||
bool ReplaceString(std::string* str, const std::string& oldSubstr, const std::string& newSubstr) {
|
||||
size_t pos = str->find(oldSubstr);
|
||||
if (pos != std::string::npos) {
|
||||
str->replace(pos, oldSubstr.length(), newSubstr);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetSplitTagsTranslation(const std::string& origText, std::string* newText, std::vector<std::string>& unTransResultRet) {
|
||||
if (!origText.contains(L'<')) return false;
|
||||
const auto splitResult = SplitByTags(origText);
|
||||
if (splitResult.empty()) return false;
|
||||
|
||||
*newText = origText;
|
||||
bool ret = true;
|
||||
for (const auto& i : splitResult) {
|
||||
if (const auto iter = genericText.find(i); iter != genericText.end()) {
|
||||
ReplaceString(newText, i, iter->second);
|
||||
}
|
||||
else {
|
||||
unTransResultRet.emplace_back(i);
|
||||
ret = false;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void LoadData() {
|
||||
static auto localizationFile = GetBasePath() / "local-files" / "localization.json";
|
||||
static auto genericFile = GetBasePath() / "local-files" / "generic.json";
|
||||
static auto genericDir = GetBasePath() / "local-files" / "genericTrans";
|
||||
|
||||
if (!exists(localizationFile)) return;
|
||||
LoadJsonDataToMap(localizationFile, i18nData, true);
|
||||
Log::InfoFmt("%ld localization items loaded.", i18nData.size());
|
||||
|
||||
LoadJsonDataToMap(genericFile, genericText, true);
|
||||
if (std::filesystem::exists(genericDir) || std::filesystem::is_directory(genericDir)) {
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(genericDir)) {
|
||||
if (std::filesystem::is_regular_file(entry.path())) {
|
||||
const auto currFile = entry.path();
|
||||
if (to_lower(currFile.extension().string()) == ".json") {
|
||||
LoadJsonDataToMap(currFile, genericText, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ProcessGenericTextLabels();
|
||||
Log::InfoFmt("%ld generic text items loaded.", genericText.size());
|
||||
|
||||
static auto dumpBasePath = GetBasePath() / "dump-files";
|
||||
@@ -132,6 +237,7 @@ namespace GakumasLocal::Local {
|
||||
|
||||
bool inDump = false;
|
||||
void DumpI18nItem(const std::string& key, const std::string& value) {
|
||||
if (!Config::dumpText) return;
|
||||
if (i18nDumpData.contains(key)) return;
|
||||
i18nDumpData[key] = value;
|
||||
Log::DebugFmt("DumpI18nItem: %s - %s", key.c_str(), value.c_str());
|
||||
@@ -176,33 +282,78 @@ namespace GakumasLocal::Local {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string GetDumpGenericFileName() {
|
||||
if (genericDumpFileIndex == 0) return "generic.json";
|
||||
return Log::StringFormat("generic_%d.json", genericDumpFileIndex);
|
||||
}
|
||||
|
||||
bool inDumpGeneric = false;
|
||||
void DumpGenericText(const std::string& origText) {
|
||||
if (translatedText.contains(origText)) return;
|
||||
|
||||
if (std::find(genericTextDumpData.begin(), genericTextDumpData.end(), origText) != genericTextDumpData.end()) {
|
||||
return;
|
||||
}
|
||||
if (IsPureStringValue(origText)) return;
|
||||
|
||||
genericTextDumpData.push_back(origText);
|
||||
static auto dumpBasePath = GetBasePath() / "dump-files";
|
||||
|
||||
if (inDumpGeneric) return;
|
||||
inDumpGeneric = true;
|
||||
std::thread([](){
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
DumpVectorDataToJson(dumpBasePath, GetDumpGenericFileName(), genericTextDumpData);
|
||||
genericTextDumpData.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;
|
||||
std::vector<std::string> unTransResultRet;
|
||||
if (GetSplitTagsTranslation(origText, newStr, unTransResultRet)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (std::find(genericTextDumpData.begin(), genericTextDumpData.end(), "") != genericTextDumpData.end()) {
|
||||
if (!Config::dumpText) {
|
||||
return false;
|
||||
}
|
||||
|
||||
genericTextDumpData.push_back(origText);
|
||||
static auto dumpBasePath = GetBasePath() / "dump-files";
|
||||
|
||||
if (inDumpGeneric) return false;
|
||||
inDumpGeneric = true;
|
||||
std::thread([](){
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
DumpVectorDataToJson(dumpBasePath, "generic.json", genericTextDumpData);
|
||||
genericTextDumpData.clear();
|
||||
inDumpGeneric = false;
|
||||
}).detach();
|
||||
if (unTransResultRet.empty()) {
|
||||
DumpGenericText(origText);
|
||||
}
|
||||
else {
|
||||
for (const auto& i : unTransResultRet) {
|
||||
DumpGenericText(i);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ChangeDumpTextIndex(int changeValue) {
|
||||
if (!Config::dumpText) return "";
|
||||
genericDumpFileIndex += changeValue;
|
||||
return Log::StringFormat("GenericDumpFile: %s", GetDumpGenericFileName().c_str());
|
||||
}
|
||||
|
||||
std::string OnKeyDown(int message, int key) {
|
||||
if (message == WM_KEYDOWN) {
|
||||
switch (key) {
|
||||
case KEY_ADD: {
|
||||
return ChangeDumpTextIndex(1);
|
||||
} break;
|
||||
case KEY_SUB: {
|
||||
return ChangeDumpTextIndex(-1);
|
||||
} break;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -12,6 +12,8 @@ namespace GakumasLocal::Local {
|
||||
|
||||
bool GetResourceText(const std::string& name, std::string* ret);
|
||||
bool GetGenericText(const std::string& origText, std::string* newStr);
|
||||
|
||||
std::string OnKeyDown(int message, int key);
|
||||
}
|
||||
|
||||
#endif //GAKUMAS_LOCALIFY_LOCAL_H
|
||||
@@ -24,6 +24,11 @@ extern jmethodID showToastMethodId;
|
||||
|
||||
|
||||
namespace GakumasLocal::Log {
|
||||
std::string StringFormat(const char* fmt, ...) {
|
||||
GetParamStringResult(result);
|
||||
return result.c_str();
|
||||
}
|
||||
|
||||
void Log(int prio, const char* msg) {
|
||||
__android_log_write(prio, "GakumasLocal-Native", msg);
|
||||
}
|
||||
@@ -73,7 +78,6 @@ namespace GakumasLocal::Log {
|
||||
if (!env) {
|
||||
return;
|
||||
}
|
||||
g_javaVM->AttachCurrentThread(&env, nullptr);
|
||||
|
||||
jclass& kotlinClass = g_gakumasHookMainClass;
|
||||
if (!kotlinClass) {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#ifndef GAKUMAS_LOCALIFY_LOG_H
|
||||
#define GAKUMAS_LOCALIFY_LOG_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace GakumasLocal::Log {
|
||||
std::string StringFormat(const char* fmt, ...);
|
||||
void LogUnityLog(int prio, const char* fmt, ...);
|
||||
void LogFmt(int prio, const char* fmt, ...);
|
||||
void Info(const char* msg);
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <jni.h>
|
||||
#include <deque>
|
||||
#include <numeric>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace GakumasLocal {
|
||||
|
||||
@@ -2,31 +2,7 @@
|
||||
#include "camera.hpp"
|
||||
#include <thread>
|
||||
#include "Misc.hpp"
|
||||
|
||||
#define KEY_W 51
|
||||
#define KEY_S 47
|
||||
#define KEY_A 29
|
||||
#define KEY_D 32
|
||||
#define KEY_R 46
|
||||
#define KEY_Q 45
|
||||
#define KEY_E 33
|
||||
#define KEY_F 34
|
||||
#define KEY_I 37
|
||||
#define KEY_K 39
|
||||
#define KEY_J 38
|
||||
#define KEY_L 40
|
||||
#define KEY_V 50
|
||||
#define KEY_UP 19
|
||||
#define KEY_DOWN 20
|
||||
#define KEY_LEFT 21
|
||||
#define KEY_RIGHT 22
|
||||
#define KEY_CTRL 113
|
||||
#define KEY_SHIFT 59
|
||||
#define KEY_ALT 57
|
||||
#define KEY_SPACE 62
|
||||
|
||||
#define WM_KEYDOWN 0
|
||||
#define WM_KEYUP 1
|
||||
#include "../BaseDefine.h"
|
||||
|
||||
|
||||
namespace GKCamera {
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
namespace GakumasLocal::Config {
|
||||
bool isConfigInit = false;
|
||||
|
||||
bool dbgMode = false;
|
||||
bool enabled = true;
|
||||
bool forceExportResource = true;
|
||||
bool textTest = false;
|
||||
bool dumpText = false;
|
||||
bool enableFreeCamera = false;
|
||||
int targetFrameRate = 0;
|
||||
bool unlockAllLive = false;
|
||||
@@ -29,8 +32,11 @@ namespace GakumasLocal::Config {
|
||||
|
||||
#define GetConfigItem(name) if (config.contains(#name)) name = config[#name]
|
||||
|
||||
GetConfigItem(dbgMode);
|
||||
GetConfigItem(enabled);
|
||||
GetConfigItem(forceExportResource);
|
||||
GetConfigItem(textTest);
|
||||
GetConfigItem(dumpText);
|
||||
GetConfigItem(targetFrameRate);
|
||||
GetConfigItem(enableFreeCamera);
|
||||
GetConfigItem(unlockAllLive);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#ifndef GAKUMAS_LOCALIFY_CONFIG_HPP
|
||||
#define GAKUMAS_LOCALIFY_CONFIG_HPP
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace GakumasLocal::Config {
|
||||
extern bool isConfigInit;
|
||||
|
||||
extern bool dbgMode;
|
||||
extern bool enabled;
|
||||
extern bool forceExportResource;
|
||||
extern bool textTest;
|
||||
extern bool dumpText;
|
||||
extern bool enableFreeCamera;
|
||||
extern int targetFrameRate;
|
||||
extern bool unlockAllLive;
|
||||
@@ -26,6 +27,3 @@ namespace GakumasLocal::Config {
|
||||
|
||||
void LoadConfig(const std::string& configStr);
|
||||
}
|
||||
|
||||
|
||||
#endif //GAKUMAS_LOCALIFY_CONFIG_HPP
|
||||
Reference in new issue
Block a user