mirror of
https://git.chinosk6.cn/chinosk/gkms-local.git
synced 2026-10-07 07:01:46 +07:00
init commit
This commit is contained in:
105 files changed
+29901
No files matched your search
@@ -0,0 +1,322 @@
|
||||
#include <android/log.h>
|
||||
#include "Hook.h"
|
||||
#include "Plugin.h"
|
||||
#include "Log.h"
|
||||
#include "../deps/UnityResolve/UnityResolve.hpp"
|
||||
#include "Il2cppUtils.hpp"
|
||||
#include "Local.h"
|
||||
#include <unordered_set>
|
||||
|
||||
|
||||
#define DEFINE_HOOK(returnType, name, params) \
|
||||
using name##_Type = returnType(*) params; \
|
||||
name##_Type name##_Addr = nullptr; \
|
||||
name##_Type name##_Orig = nullptr; \
|
||||
returnType name##_Hook params
|
||||
|
||||
|
||||
#define ADD_HOOK(name, addr) \
|
||||
name##_Addr = reinterpret_cast<name##_Type>(addr); \
|
||||
if (addr) { \
|
||||
hookInstaller->InstallHook(reinterpret_cast<void*>(addr), \
|
||||
reinterpret_cast<void*>(name##_Hook), \
|
||||
reinterpret_cast<void**>(&name##_Orig)); \
|
||||
GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr); \
|
||||
} \
|
||||
else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr)
|
||||
|
||||
|
||||
namespace GakumasLocal::HookMain {
|
||||
using Il2cppString = UnityResolve::UnityType::String;
|
||||
|
||||
UnityResolve::UnityType::String* environment_get_stacktrace() {
|
||||
/*
|
||||
static auto mtd = Il2cppUtils::GetMethod("mscorlib.dll", "System",
|
||||
"Environment", "get_StackTrace");
|
||||
return mtd->Invoke<UnityResolve::UnityType::String*>();*/
|
||||
const auto pClass = Il2cppUtils::GetClass("mscorlib.dll", "System.Diagnostics",
|
||||
"StackTrace");
|
||||
|
||||
const auto ctor_mtd = Il2cppUtils::GetMethod("mscorlib.dll", "System.Diagnostics",
|
||||
"StackTrace", ".ctor");
|
||||
const auto toString_mtd = Il2cppUtils::GetMethod("mscorlib.dll", "System.Diagnostics",
|
||||
"StackTrace", "ToString");
|
||||
|
||||
const auto klassInstance = pClass->New<void*>();
|
||||
ctor_mtd->Invoke<void>(klassInstance);
|
||||
return toString_mtd->Invoke<Il2cppString*>(klassInstance);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, Internal_LogException, (void* ex, void* obj)) {
|
||||
Internal_LogException_Orig(ex, obj);
|
||||
// Log::LogFmt(ANDROID_LOG_VERBOSE, "UnityLog - Internal_LogException");
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, Internal_Log, (int logType, int logOption, UnityResolve::UnityType::String* content, void* context)) {
|
||||
Internal_Log_Orig(logType, logOption, content, context);
|
||||
// 2022.3.21f1
|
||||
// Log::LogFmt(ANDROID_LOG_VERBOSE, "UnityLog - Internal_Log: %s", content->ToString().c_str());
|
||||
}
|
||||
|
||||
std::unordered_map<void*, std::string> loadHistory{};
|
||||
|
||||
DEFINE_HOOK(void*, AssetBundle_LoadAssetAsync, (void* _this, Il2cppString* name, void* type)) {
|
||||
// Log::InfoFmt("AssetBundle_LoadAssetAsync: %s, type: %s", name->ToString().c_str());
|
||||
auto ret = AssetBundle_LoadAssetAsync_Orig(_this, name, type);
|
||||
loadHistory.emplace(ret, name->ToString());
|
||||
return ret;
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void*, AssetBundleRequest_GetResult, (void* _this)) {
|
||||
auto result = AssetBundleRequest_GetResult_Orig(_this);
|
||||
if (const auto iter = loadHistory.find(_this); iter != loadHistory.end()) {
|
||||
const auto name = iter->second;
|
||||
loadHistory.erase(iter);
|
||||
|
||||
// const auto assetClass = Il2cppUtils::get_class_from_instance(result);
|
||||
// Log::InfoFmt("AssetBundleRequest_GetResult: %s, type: %s", name.c_str(), static_cast<Il2CppClassHead*>(assetClass)->name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void*, Resources_Load, (Il2cppString* path, void* systemTypeInstance)) {
|
||||
auto ret = Resources_Load_Orig(path, systemTypeInstance);
|
||||
|
||||
// if (ret) Log::DebugFmt("Resources_Load: %s, type: %s", path->ToString().c_str(), Il2cppUtils::get_class_from_instance(ret)->name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, I18nHelper_SetUpI18n, (void* _this, Il2cppString* lang, Il2cppString* localizationText, int keyComparison)) {
|
||||
// Log::InfoFmt("SetUpI18n lang: %s, key: %d text: %s", lang->ToString().c_str(), keyComparison, localizationText->ToString().c_str());
|
||||
// TODO 此处为 dump 原文 csv
|
||||
I18nHelper_SetUpI18n_Orig(_this, lang, localizationText, keyComparison);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, I18nHelper_SetValue, (void* _this, Il2cppString* key, Il2cppString* value)) {
|
||||
// Log::InfoFmt("I18nHelper_SetValue: %s - %s", key->ToString().c_str(), value->ToString().c_str());
|
||||
std::string local;
|
||||
if (Local::GetI18n(key->ToString(), &local)) {
|
||||
I18nHelper_SetValue_Orig(_this, key, UnityResolve::UnityType::String::New(local));
|
||||
return;
|
||||
}
|
||||
Local::DumpI18nItem(key->ToString(), value->ToString());
|
||||
I18nHelper_SetValue_Orig(_this, key, value);
|
||||
}
|
||||
|
||||
void* fontCache = nullptr;
|
||||
void* GetReplaceFont() {
|
||||
static auto CreateFontFromPath = reinterpret_cast<void (*)(void* self, Il2cppString* path)>(
|
||||
Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Font::Internal_CreateFontFromPath(UnityEngine.Font,System.String)")
|
||||
);
|
||||
static auto Font_klass = Il2cppUtils::GetClass("UnityEngine.TextRenderingModule.dll",
|
||||
"UnityEngine", "Font");
|
||||
static auto Font_ctor = Il2cppUtils::GetMethod("UnityEngine.TextRenderingModule.dll",
|
||||
"UnityEngine", "Font", ".ctor");
|
||||
static auto IsNativeObjectAlive = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll", "UnityEngine",
|
||||
"Object", "IsNativeObjectAlive");
|
||||
if (fontCache) {
|
||||
if (IsNativeObjectAlive->Invoke<bool>(fontCache)) {
|
||||
return fontCache;
|
||||
}
|
||||
}
|
||||
|
||||
const auto newFont = Font_klass->New<void*>();
|
||||
Font_ctor->Invoke<void>(newFont);
|
||||
|
||||
static std::string fontName = Local::GetBasePath() / "local-files" / "MO-UDShinGo-SC-Gb4-M.otf";
|
||||
CreateFontFromPath(newFont, Il2cppString::New(fontName));
|
||||
fontCache = newFont;
|
||||
return newFont;
|
||||
}
|
||||
|
||||
std::unordered_set<void*> updatedFontPtrs{};
|
||||
void UpdateFont(void* TMP_Text_this) {
|
||||
static auto get_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
|
||||
"TMPro", "TMP_Text", "get_font");
|
||||
static auto set_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
|
||||
"TMPro", "TMP_Text", "set_font");
|
||||
|
||||
static auto set_sourceFontFile = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_FontAsset", "set_sourceFontFile");
|
||||
static auto UpdateFontAssetData = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_FontAsset", "UpdateFontAssetData");
|
||||
|
||||
auto fontAsset = get_font->Invoke<void*>(TMP_Text_this);
|
||||
auto newFont = GetReplaceFont();
|
||||
if (fontAsset && newFont) {
|
||||
set_sourceFontFile->Invoke<void>(fontAsset, newFont);
|
||||
if (!updatedFontPtrs.contains(fontAsset)) {
|
||||
updatedFontPtrs.emplace(fontAsset);
|
||||
UpdateFontAssetData->Invoke<void>(fontAsset);
|
||||
}
|
||||
}
|
||||
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());
|
||||
std::string transText;
|
||||
if (Local::GetGenericText(text->ToString(), &transText)) {
|
||||
return TMP_Text_set_text_Orig(_this, UnityResolve::UnityType::String::New(transText));
|
||||
}
|
||||
|
||||
TMP_Text_set_text_Orig(_this, UnityResolve::UnityType::String::New("[TS]" + text->ToString()));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, TextMeshProUGUI_Awake, (void* _this, void* method)) {
|
||||
// Log::InfoFmt("TextMeshProUGUI_Awake at %p, _this at %p", TextMeshProUGUI_Awake_Orig, _this);
|
||||
|
||||
const auto TMP_Text_klass = Il2cppUtils::GetClass("Unity.TextMeshPro.dll",
|
||||
"TMPro", "TMP_Text");
|
||||
const auto get_Text_method = TMP_Text_klass->Get<UnityResolve::Method>("get_text");
|
||||
const auto set_Text_method = TMP_Text_klass->Get<UnityResolve::Method>("set_text");
|
||||
const auto currText = get_Text_method->Invoke<UnityResolve::UnityType::String*>(_this);
|
||||
if (currText) {
|
||||
//Log::InfoFmt("TextMeshProUGUI_Awake: %s", currText->ToString().c_str());
|
||||
std::string transText;
|
||||
if (Local::GetGenericText(currText->ToString(), &transText)) {
|
||||
TMP_Text_set_text_Orig(_this, UnityResolve::UnityType::String::New(transText));
|
||||
}
|
||||
}
|
||||
|
||||
// set_font->Invoke<void>(_this, font);
|
||||
UpdateFont(_this);
|
||||
TextMeshProUGUI_Awake_Orig(_this, method);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, UI_Text_set_text, (void* _this, Il2cppString* value)) {
|
||||
UI_Text_set_text_Orig(_this, Il2cppString::New("[US]" + value->ToString()));
|
||||
|
||||
static auto set_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
|
||||
"TMP_Text", "set_font");
|
||||
auto newFont = GetReplaceFont();
|
||||
set_font->Invoke<void>(_this, newFont);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(Il2cppString*, OctoCaching_GetResourceFileName, (void* data, void* method)) {
|
||||
auto ret = OctoCaching_GetResourceFileName_Orig(data, method);
|
||||
//Log::DebugFmt("OctoCaching_GetResourceFileName: %s", ret->ToString().c_str());
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, OctoResourceLoader_LoadFromCacheOrDownload,
|
||||
(void* _this, Il2cppString* resourceName, void* onComplete, void* onProgress, void* method)) {
|
||||
|
||||
Log::DebugFmt("OctoResourceLoader_LoadFromCacheOrDownload: %s\n", resourceName->ToString().c_str());
|
||||
|
||||
std::string replaceStr;
|
||||
if (Local::GetResourceText(resourceName->ToString(), &replaceStr)) {
|
||||
const auto onComplete_klass = Il2cppUtils::get_class_from_instance(onComplete);
|
||||
const auto onComplete_invoke_mtd = UnityResolve::Invoke<Il2cppUtils::MethodInfo*>(
|
||||
"il2cpp_class_get_method_from_name", onComplete_klass, "Invoke", 2);
|
||||
if (onComplete_invoke_mtd) {
|
||||
const auto onComplete_invoke = reinterpret_cast<void (*)(void*, Il2cppString*, void*)>(
|
||||
onComplete_invoke_mtd->methodPointer
|
||||
);
|
||||
onComplete_invoke(onComplete, UnityResolve::UnityType::String::New(replaceStr), NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return OctoResourceLoader_LoadFromCacheOrDownload_Orig(_this, resourceName, onComplete, onProgress, method);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, OnDownloadProgress_Invoke, (void* _this, Il2cppString* name, uint64_t receivedLength, uint64_t contentLength)) {
|
||||
Log::DebugFmt("OnDownloadProgress_Invoke: %s, %lu/%lu", name->ToString().c_str(), receivedLength, contentLength);
|
||||
OnDownloadProgress_Invoke_Orig(_this, name, receivedLength, contentLength);
|
||||
}
|
||||
|
||||
// UnHooked
|
||||
DEFINE_HOOK(UnityResolve::UnityType::String*, UI_I18n_GetOrDefault, (void* _this,
|
||||
UnityResolve::UnityType::String* key, UnityResolve::UnityType::String* defaultKey, void* method)) {
|
||||
|
||||
auto ret = UI_I18n_GetOrDefault_Orig(_this, key, defaultKey, method);
|
||||
|
||||
// Log::DebugFmt("UI_I18n_GetOrDefault: key: %s, default: %s, result: %s", key->ToString().c_str(), defaultKey->ToString().c_str(), ret->ToString().c_str());
|
||||
|
||||
return ret;
|
||||
// return UnityResolve::UnityType::String::New("[I18]" + ret->ToString());
|
||||
}
|
||||
|
||||
void StartInjectFunctions() {
|
||||
const auto hookInstaller = Plugin::GetInstance().GetHookInstaller();
|
||||
UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW), UnityResolve::Mode::Il2Cpp);
|
||||
|
||||
ADD_HOOK(AssetBundle_LoadAssetAsync, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.AssetBundle::LoadAssetAsync_Internal(System.String,System.Type)"));
|
||||
ADD_HOOK(AssetBundleRequest_GetResult, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.AssetBundleRequest::GetResult()"));
|
||||
ADD_HOOK(Resources_Load, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.ResourcesAPIInternal::Load(System.String,System.Type)"));
|
||||
|
||||
ADD_HOOK(I18nHelper_SetUpI18n, Il2cppUtils::GetMethodPointer("quaunity-ui.Runtime.dll", "Qua.UI",
|
||||
"I18nHelper", "SetUpI18n"));
|
||||
ADD_HOOK(I18nHelper_SetValue, Il2cppUtils::GetMethodPointer("quaunity-ui.Runtime.dll", "Qua.UI",
|
||||
"I18n", "SetValue"));
|
||||
|
||||
//ADD_HOOK(UI_I18n_GetOrDefault, Il2cppUtils::GetMethodPointer("quaunity-ui.Runtime.dll", "Qua.UI",
|
||||
// "I18n", "GetOrDefault"));
|
||||
|
||||
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(UI_Text_set_text, Il2cppUtils::GetMethodPointer("UnityEngine.UI.dll", "UnityEngine.UI",
|
||||
"Text", "set_text"));
|
||||
|
||||
ADD_HOOK(OctoCaching_GetResourceFileName, Il2cppUtils::GetMethodPointer("Octo.dll", "Octo.Caching",
|
||||
"OctoCaching", "GetResourceFileName"));
|
||||
|
||||
ADD_HOOK(OctoResourceLoader_LoadFromCacheOrDownload,
|
||||
Il2cppUtils::GetMethodPointer("Octo.dll", "Octo.Loader",
|
||||
"OctoResourceLoader", "LoadFromCacheOrDownload",
|
||||
{"System.String", "System.Action<System.String,Octo.LoadError>", "Octo.OnDownloadProgress"}));
|
||||
|
||||
ADD_HOOK(OnDownloadProgress_Invoke,
|
||||
Il2cppUtils::GetMethodPointer("Octo.dll", "Octo",
|
||||
"OnDownloadProgress", "Invoke"));
|
||||
|
||||
ADD_HOOK(Internal_LogException, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.DebugLogHandler::Internal_LogException(System.Exception,UnityEngine.Object)"));
|
||||
ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.DebugLogHandler::Internal_Log(UnityEngine.LogType,UnityEngine.LogOption,System.String,UnityEngine.Object)"));
|
||||
}
|
||||
// 77 2640 5000
|
||||
|
||||
DEFINE_HOOK(int, il2cpp_init, (const char* domain_name)) {
|
||||
const auto ret = il2cpp_init_Orig(domain_name);
|
||||
// InjectFunctions();
|
||||
|
||||
Log::Info("Start init plugin...");
|
||||
|
||||
StartInjectFunctions();
|
||||
Local::LoadData();
|
||||
|
||||
Log::Info("Plugin init finished.");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace GakumasLocal::Hook {
|
||||
void Install() {
|
||||
const auto hookInstaller = Plugin::GetInstance().GetHookInstaller();
|
||||
|
||||
Log::Info("Installing hook");
|
||||
|
||||
ADD_HOOK(HookMain::il2cpp_init,
|
||||
Plugin::GetInstance().GetHookInstaller()->LookupSymbol("il2cpp_init"));
|
||||
|
||||
Log::Info("Hook installed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#ifndef GAKUMAS_LOCALIFY_HOOK_H
|
||||
#define GAKUMAS_LOCALIFY_HOOK_H
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace GakumasLocal::Hook
|
||||
{
|
||||
void Install();
|
||||
}
|
||||
|
||||
#endif //GAKUMAS_LOCALIFY_HOOK_H
|
||||
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
#include "../deps/UnityResolve/UnityResolve.hpp"
|
||||
#include "Log.h"
|
||||
#include <memory>
|
||||
|
||||
namespace Il2cppUtils {
|
||||
using namespace GakumasLocal;
|
||||
|
||||
struct Il2CppClassHead {
|
||||
// The following fields are always valid for a Il2CppClass structure
|
||||
const void* image;
|
||||
void* gc_desc;
|
||||
const char* name;
|
||||
const char* namespaze;
|
||||
};
|
||||
|
||||
struct MethodInfo {
|
||||
uintptr_t methodPointer;
|
||||
uintptr_t invoker_method;
|
||||
const char* name;
|
||||
uintptr_t klass;
|
||||
//const Il2CppType* return_type;
|
||||
//const ParameterInfo* parameters;
|
||||
const void* return_type;
|
||||
const void* parameters;
|
||||
uintptr_t methodDefinition;
|
||||
uintptr_t genericContainer;
|
||||
uint32_t token;
|
||||
uint16_t flags;
|
||||
uint16_t iflags;
|
||||
uint16_t slot;
|
||||
uint8_t parameters_count;
|
||||
uint8_t is_generic : 1;
|
||||
uint8_t is_inflated : 1;
|
||||
uint8_t wrapper_type : 1;
|
||||
uint8_t is_marshaled_from_native : 1;
|
||||
};
|
||||
|
||||
UnityResolve::Class* GetClass(const std::string& assemblyName, const std::string& nameSpaceName,
|
||||
const std::string& className) {
|
||||
const auto assembly = UnityResolve::Get(assemblyName);
|
||||
if (!assembly) {
|
||||
Log::ErrorFmt("GetMethodPointer error: assembly %s not found.", assemblyName.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
const auto pClass = assembly->Get(className, nameSpaceName);
|
||||
if (!pClass) {
|
||||
Log::ErrorFmt("GetMethodPointer error: Class %s::%s not found.", nameSpaceName.c_str(), className.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
return pClass;
|
||||
}
|
||||
/*
|
||||
UnityResolve::Method* GetMethodIl2cpp(const char* assemblyName, const char* nameSpaceName,
|
||||
const char* className, const char* methodName, const int argsCount) {
|
||||
auto domain = UnityResolve::Invoke<void*>("il2cpp_domain_get");
|
||||
UnityResolve::Invoke<void*>("il2cpp_thread_attach", domain);
|
||||
auto image = UnityResolve::Invoke<void*>("il2cpp_assembly_get_image", domain);
|
||||
if (!image) {
|
||||
Log::ErrorFmt("GetMethodIl2cpp error: assembly %s not found.", assemblyName);
|
||||
return nullptr;
|
||||
}
|
||||
Log::Debug("GetMethodIl2cpp 1");
|
||||
auto klass = UnityResolve::Invoke<void*>("il2cpp_class_from_name", image, nameSpaceName, className);
|
||||
if (!klass) {
|
||||
Log::ErrorFmt("GetMethodIl2cpp error: Class %s::%s not found.", nameSpaceName, className);
|
||||
return nullptr;
|
||||
}
|
||||
Log::Debug("GetMethodIl2cpp 2");
|
||||
auto ret = UnityResolve::Invoke<UnityResolve::Method*>("il2cpp_class_get_method_from_name", klass, methodName, argsCount);
|
||||
if (!ret) {
|
||||
Log::ErrorFmt("GetMethodIl2cpp error: method %s::%s.%s not found.", nameSpaceName, className, methodName);
|
||||
return nullptr;
|
||||
}
|
||||
return ret;
|
||||
}*/
|
||||
|
||||
UnityResolve::Method* GetMethod(const std::string& assemblyName, const std::string& nameSpaceName,
|
||||
const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
|
||||
const auto assembly = UnityResolve::Get(assemblyName);
|
||||
if (!assembly) {
|
||||
Log::ErrorFmt("GetMethod error: assembly %s not found.", assemblyName.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
const auto pClass = assembly->Get(className, nameSpaceName);
|
||||
if (!pClass) {
|
||||
Log::ErrorFmt("GetMethod error: Class %s::%s not found.", nameSpaceName.c_str(), className.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
auto method = pClass->Get<UnityResolve::Method>(methodName, args);
|
||||
if (!method) {
|
||||
/*
|
||||
method = GetMethodIl2cpp(assemblyName.c_str(), nameSpaceName.c_str(), className.c_str(),
|
||||
methodName.c_str(), args.size() == 0 ? -1 : args.size());
|
||||
if (!method) {
|
||||
Log::ErrorFmt("GetMethod error: method %s::%s.%s not found.", nameSpaceName.c_str(), className.c_str(), methodName.c_str());
|
||||
return nullptr;
|
||||
}*/
|
||||
Log::ErrorFmt("GetMethod error: method %s::%s.%s not found.", nameSpaceName.c_str(), className.c_str(), methodName.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
void* GetMethodPointer(const std::string& assemblyName, const std::string& nameSpaceName,
|
||||
const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
|
||||
auto method = GetMethod(assemblyName, nameSpaceName, className, methodName, args);
|
||||
if (method) {
|
||||
return method->function;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* il2cpp_resolve_icall(const char* s) {
|
||||
return UnityResolve::Invoke<void*>("il2cpp_resolve_icall", s);
|
||||
}
|
||||
|
||||
Il2CppClassHead* get_class_from_instance(const void* instance) {
|
||||
return static_cast<Il2CppClassHead*>(*static_cast<void* const*>(std::assume_aligned<alignof(void*)>(instance)));
|
||||
}
|
||||
|
||||
|
||||
void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate)
|
||||
{
|
||||
void* iter{};
|
||||
while (const auto curNestedClass = UnityResolve::Invoke<void*>("il2cpp_class_get_nested_types", klass, &iter))
|
||||
{
|
||||
if (static_cast<decltype(predicate)>(predicate)(curNestedClass))
|
||||
{
|
||||
return curNestedClass;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* find_nested_class_from_name(void* klass, const char* name)
|
||||
{
|
||||
return find_nested_class(klass, [name = std::string_view(name)](void* nestedClass) {
|
||||
return static_cast<Il2CppClassHead*>(nestedClass)->name == name;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
#include "Local.h"
|
||||
#include "Log.h"
|
||||
#include "Plugin.h"
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <thread>
|
||||
|
||||
|
||||
namespace GakumasLocal::Local {
|
||||
std::unordered_map<std::string, std::string> i18nData{};
|
||||
std::unordered_map<std::string, std::string> i18nDumpData{};
|
||||
std::unordered_map<std::string, std::string> genericText{};
|
||||
std::unordered_map<std::string, std::string> genericTextDumpData{};
|
||||
std::unordered_set<std::string> translatedText{};
|
||||
|
||||
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) {
|
||||
if (!exists(filePath)) return;
|
||||
try {
|
||||
dict.clear();
|
||||
std::ifstream file(filePath);
|
||||
if (!file.is_open()) {
|
||||
Log::ErrorFmt("Load %s failed.\n", filePath.c_str());
|
||||
return;
|
||||
}
|
||||
std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
file.close();
|
||||
auto fileData = nlohmann::json::parse(fileContent);
|
||||
for (auto& i : fileData.items()) {
|
||||
const auto& key = i.key();
|
||||
const std::string value = i.value();
|
||||
if (insertToTranslated) translatedText.emplace(value);
|
||||
dict[key] = value;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
Log::ErrorFmt("Load %s failed: %s\n", filePath.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void DumpMapDataToJson(const std::filesystem::path& dumpBasePath, const std::filesystem::path& fileName,
|
||||
const std::unordered_map<std::string, std::string>& dict) {
|
||||
const auto dumpFilePath = dumpBasePath / fileName;
|
||||
try {
|
||||
if (!is_directory(dumpBasePath)) {
|
||||
std::filesystem::create_directories(dumpBasePath);
|
||||
}
|
||||
if (!std::filesystem::exists(dumpFilePath)) {
|
||||
std::ofstream dumpWriteLrcFile(dumpFilePath, std::ofstream::out);
|
||||
dumpWriteLrcFile << "{}";
|
||||
dumpWriteLrcFile.close();
|
||||
}
|
||||
|
||||
std::ifstream dumpLrcFile(dumpFilePath);
|
||||
std::string fileContent((std::istreambuf_iterator<char>(dumpLrcFile)), std::istreambuf_iterator<char>());
|
||||
dumpLrcFile.close();
|
||||
auto fileData = nlohmann::ordered_json::parse(fileContent);
|
||||
for (const auto& i : dict) {
|
||||
fileData[i.first] = i.second;
|
||||
}
|
||||
const auto newStr = fileData.dump(4, 32, false);
|
||||
std::ofstream dumpWriteLrcFile(dumpFilePath, std::ofstream::out);
|
||||
dumpWriteLrcFile << newStr.c_str();
|
||||
dumpWriteLrcFile.close();
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
Log::ErrorFmt("DumpMapDataToJson %s failed: %s", dumpFilePath.c_str(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void LoadData() {
|
||||
static auto localizationFile = GetBasePath() / "local-files" / "localization.json";
|
||||
static auto genericFile = GetBasePath() / "local-files" / "generic.json";
|
||||
|
||||
if (!exists(localizationFile)) return;
|
||||
LoadJsonDataToMap(localizationFile, i18nData, true);
|
||||
Log::InfoFmt("%ld localization items loaded.", i18nData.size());
|
||||
|
||||
LoadJsonDataToMap(genericFile, genericText, true);
|
||||
Log::InfoFmt("%ld generic text items loaded.", genericText.size());
|
||||
|
||||
static auto dumpBasePath = GetBasePath() / "dump-files";
|
||||
static auto dumpFilePath = dumpBasePath / "localization.json";
|
||||
LoadJsonDataToMap(dumpFilePath, i18nDumpData);
|
||||
}
|
||||
|
||||
bool GetI18n(const std::string& key, std::string* ret) {
|
||||
if (const auto iter = i18nData.find(key); iter != i18nData.end()) {
|
||||
*ret = iter->second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool inDump = false;
|
||||
void DumpI18nItem(const std::string& key, const std::string& value) {
|
||||
if (i18nDumpData.contains(key)) return;
|
||||
i18nDumpData[key] = value;
|
||||
Log::DebugFmt("DumpI18nItem: %s - %s", key.c_str(), value.c_str());
|
||||
|
||||
static auto dumpBasePath = GetBasePath() / "dump-files";
|
||||
|
||||
if (inDump) return;
|
||||
inDump = true;
|
||||
std::thread([](){
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
DumpMapDataToJson(dumpBasePath, "localization.json", i18nDumpData);
|
||||
inDump = false;
|
||||
}).detach();
|
||||
}
|
||||
|
||||
std::string readFileToString(const std::string& filename) {
|
||||
std::ifstream file(filename);
|
||||
if (!file.is_open()) {
|
||||
throw std::exception();
|
||||
}
|
||||
std::string content((std::istreambuf_iterator<char>(file)),
|
||||
(std::istreambuf_iterator<char>()));
|
||||
file.close();
|
||||
return content;
|
||||
}
|
||||
|
||||
bool GetResourceText(const std::string& name, std::string* ret) {
|
||||
static std::filesystem::path basePath = GetBasePath();
|
||||
|
||||
try {
|
||||
const auto targetFilePath = basePath / "local-files" / "resource" / name;
|
||||
// Log::DebugFmt("GetResourceText: %s", targetFilePath.c_str());
|
||||
if (exists(targetFilePath)) {
|
||||
auto readStr = readFileToString(targetFilePath);
|
||||
*ret = readStr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
Log::ErrorFmt("read file: %s failed.", name.c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool inDumpGeneric = false;
|
||||
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;
|
||||
|
||||
genericTextDumpData.emplace(origText, origText);
|
||||
static auto dumpBasePath = GetBasePath() / "dump-files";
|
||||
|
||||
if (inDumpGeneric) return false;
|
||||
inDumpGeneric = true;
|
||||
std::thread([](){
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
DumpMapDataToJson(dumpBasePath, "generic.json", genericTextDumpData);
|
||||
genericTextDumpData.clear();
|
||||
inDumpGeneric = false;
|
||||
}).detach();
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef GAKUMAS_LOCALIFY_LOCAL_H
|
||||
#define GAKUMAS_LOCALIFY_LOCAL_H
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
namespace GakumasLocal::Local {
|
||||
std::filesystem::path GetBasePath();
|
||||
void LoadData();
|
||||
bool GetI18n(const std::string& key, std::string* ret);
|
||||
void DumpI18nItem(const std::string& key, const std::string& value);
|
||||
|
||||
bool GetResourceText(const std::string& name, std::string* ret);
|
||||
bool GetGenericText(const std::string& origText, std::string* newStr);
|
||||
}
|
||||
|
||||
#endif //GAKUMAS_LOCALIFY_LOCAL_H
|
||||
@@ -0,0 +1,142 @@
|
||||
#include "Log.h"
|
||||
#include <android/log.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
std::string format(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
|
||||
// 计算格式化后的字符串长度
|
||||
int size = vsnprintf(nullptr, 0, fmt, args_copy) + 1; // 加上额外的终止符空间
|
||||
va_end(args_copy);
|
||||
|
||||
// 动态分配缓冲区
|
||||
char* buffer = new char[size];
|
||||
|
||||
// 格式化字符串
|
||||
vsnprintf(buffer, size, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
std::string result(buffer);
|
||||
delete[] buffer; // 释放缓冲区
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
namespace GakumasLocal::Log {
|
||||
void Log(int prio, const char* msg) {
|
||||
__android_log_write(prio, "GakumasLocal-Native", msg);
|
||||
}
|
||||
|
||||
void LogFmt(int prio, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
|
||||
// 计算格式化后的字符串长度
|
||||
int size = vsnprintf(nullptr, 0, fmt, args_copy) + 1; // 加上额外的终止符空间
|
||||
va_end(args_copy);
|
||||
|
||||
// 动态分配缓冲区
|
||||
char* buffer = new char[size];
|
||||
|
||||
// 格式化字符串
|
||||
vsnprintf(buffer, size, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
std::string result(buffer);
|
||||
delete[] buffer; // 释放缓冲区
|
||||
|
||||
Log(prio, result.c_str());
|
||||
}
|
||||
|
||||
void Info(const char* msg) {
|
||||
Log(ANDROID_LOG_INFO, msg);
|
||||
}
|
||||
|
||||
void InfoFmt(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
|
||||
// 计算格式化后的字符串长度
|
||||
int size = vsnprintf(nullptr, 0, fmt, args_copy) + 1; // 加上额外的终止符空间
|
||||
va_end(args_copy);
|
||||
|
||||
// 动态分配缓冲区
|
||||
char* buffer = new char[size];
|
||||
|
||||
// 格式化字符串
|
||||
vsnprintf(buffer, size, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
std::string result(buffer);
|
||||
delete[] buffer; // 释放缓冲区
|
||||
|
||||
Info(result.c_str());
|
||||
}
|
||||
|
||||
void Error(const char* msg) {
|
||||
Log(ANDROID_LOG_ERROR, msg);
|
||||
}
|
||||
|
||||
void ErrorFmt(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
|
||||
// 计算格式化后的字符串长度
|
||||
int size = vsnprintf(nullptr, 0, fmt, args_copy) + 1; // 加上额外的终止符空间
|
||||
va_end(args_copy);
|
||||
|
||||
// 动态分配缓冲区
|
||||
char* buffer = new char[size];
|
||||
|
||||
// 格式化字符串
|
||||
vsnprintf(buffer, size, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
std::string result(buffer);
|
||||
delete[] buffer; // 释放缓冲区
|
||||
|
||||
Error(result.c_str());
|
||||
}
|
||||
|
||||
void Debug(const char* msg) {
|
||||
Log(ANDROID_LOG_DEBUG, msg);
|
||||
}
|
||||
|
||||
void DebugFmt(const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
|
||||
// 计算格式化后的字符串长度
|
||||
int size = vsnprintf(nullptr, 0, fmt, args_copy) + 1; // 加上额外的终止符空间
|
||||
va_end(args_copy);
|
||||
|
||||
// 动态分配缓冲区
|
||||
char* buffer = new char[size];
|
||||
|
||||
// 格式化字符串
|
||||
vsnprintf(buffer, size, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
std::string result(buffer);
|
||||
delete[] buffer; // 释放缓冲区
|
||||
|
||||
Debug(result.c_str());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#ifndef GAKUMAS_LOCALIFY_LOG_H
|
||||
#define GAKUMAS_LOCALIFY_LOG_H
|
||||
|
||||
namespace GakumasLocal::Log {
|
||||
void LogFmt(int prio, const char* fmt, ...);
|
||||
void Info(const char* msg);
|
||||
void InfoFmt(const char* fmt, ...);
|
||||
void Error(const char* msg);
|
||||
void ErrorFmt(const char* fmt, ...);
|
||||
void Debug(const char* msg);
|
||||
void DebugFmt(const char* fmt, ...);
|
||||
}
|
||||
|
||||
#endif //GAKUMAS_LOCALIFY_LOG_H
|
||||
@@ -0,0 +1,17 @@
|
||||
#include "Misc.h"
|
||||
|
||||
#include <codecvt>
|
||||
#include <locale>
|
||||
|
||||
|
||||
namespace GakumasLocal::Misc {
|
||||
std::u16string ToUTF16(const std::string_view& str) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
|
||||
return utf16conv.from_bytes(str.data(), str.data() + str.size());
|
||||
}
|
||||
|
||||
std::string ToUTF8(const std::u16string_view& str) {
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
|
||||
return utf16conv.to_bytes(str.data(), str.data() + str.size());
|
||||
}
|
||||
} // namespace UmaPyogin::Misc
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef GAKUMAS_LOCALIFY_MISC_H
|
||||
#define GAKUMAS_LOCALIFY_MISC_H
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace GakumasLocal {
|
||||
using OpaqueFunctionPointer = void (*)();
|
||||
|
||||
namespace Misc {
|
||||
std::u16string ToUTF16(const std::string_view& str);
|
||||
std::string ToUTF8(const std::u16string_view& str);
|
||||
}
|
||||
}
|
||||
|
||||
#endif //GAKUMAS_LOCALIFY_MISC_H
|
||||
@@ -0,0 +1,24 @@
|
||||
#include "Plugin.h"
|
||||
#include "Hook.h"
|
||||
|
||||
namespace GakumasLocal {
|
||||
HookInstaller::~HookInstaller() {
|
||||
}
|
||||
|
||||
Plugin &Plugin::GetInstance() {
|
||||
static Plugin instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void Plugin::InstallHook(std::unique_ptr<HookInstaller>&& hookInstaller)
|
||||
{
|
||||
m_HookInstaller = std::move(hookInstaller);
|
||||
Hook::Install();
|
||||
}
|
||||
|
||||
HookInstaller* Plugin::GetHookInstaller() const
|
||||
{
|
||||
return m_HookInstaller.get();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#ifndef GAKUMAS_LOCALIFY_PLUGIN_H
|
||||
#define GAKUMAS_LOCALIFY_PLUGIN_H
|
||||
|
||||
#include "Misc.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace GakumasLocal {
|
||||
struct HookInstaller
|
||||
{
|
||||
virtual ~HookInstaller();
|
||||
virtual void InstallHook(void* addr, void* hook, void** orig) = 0;
|
||||
virtual OpaqueFunctionPointer LookupSymbol(const char* name) = 0;
|
||||
|
||||
std::string m_il2cppLibraryPath;
|
||||
std::string localizationFilesDir;
|
||||
};
|
||||
|
||||
class Plugin
|
||||
{
|
||||
public:
|
||||
static Plugin& GetInstance();
|
||||
|
||||
void InstallHook(std::unique_ptr<HookInstaller>&& hookInstaller);
|
||||
|
||||
HookInstaller* GetHookInstaller() const;
|
||||
|
||||
Plugin(Plugin const&) = delete;
|
||||
Plugin& operator=(Plugin const&) = delete;
|
||||
|
||||
private:
|
||||
Plugin() = default;
|
||||
|
||||
std::unique_ptr<HookInstaller> m_HookInstaller;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //GAKUMAS_LOCALIFY_PLUGIN_H
|
||||
Reference in new issue
Block a user