diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..fb89e8f
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+app/src/main/cpp/deps/* linguist-language=ico
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c4d7741
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
+/.idea
+/.vs
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..466c758
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "app/src/main/assets/gakumas-local"]
+ path = app/src/main/assets/gakumas-local
+ url = https://github.com/chinosk6/GakumasTranslationData.git
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..a757d4c
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,92 @@
+plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+}
+android.buildFeatures.buildConfig true
+
+android {
+ namespace 'io.github.chinosk.gakumas.localify'
+ compileSdk 34
+ ndkVersion "26.3.11579264"
+
+ defaultConfig {
+ applicationId "io.github.chinosk.gakumas.localify"
+ minSdk 24
+ targetSdk 34
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ vectorDrawables {
+ useSupportLibrary true
+ }
+ externalNativeBuild {
+ cmake {
+ cppFlags ''
+ }
+ }
+ ndk {
+ abiFilters 'arm64-v8a'
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ buildConfigField "boolean", "ENABLE_LOG", "true"
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ buildFeatures {
+ compose true
+ prefab true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.5.1'
+ }
+ packaging {
+ resources {
+ excludes += '/META-INF/{AL2.0,LGPL2.1}'
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ path file('src/main/cpp/CMakeLists.txt')
+ version '3.22.1'
+ }
+ }
+ packagingOptions {
+ pickFirst '**/libxdl.so'
+ pickFirst '**/libshadowhook.so'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
+ implementation 'androidx.activity:activity-compose:1.8.2'
+ implementation platform('androidx.compose:compose-bom:2023.08.00')
+ implementation 'androidx.compose.ui:ui'
+ implementation 'androidx.compose.ui:ui-graphics'
+ implementation 'androidx.compose.ui:ui-tooling-preview'
+ implementation 'androidx.compose.material3:material3'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+ androidTestImplementation platform('androidx.compose:compose-bom:2023.08.00')
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
+ debugImplementation 'androidx.compose.ui:ui-tooling'
+ debugImplementation 'androidx.compose.ui:ui-test-manifest'
+
+ implementation 'io.github.hexhacking:xdl:2.1.1'
+ implementation 'com.bytedance.android:shadowhook:1.0.9'
+ compileOnly 'de.robv.android.xposed:api:82'
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/io/github/chinosk/gakumas/localify/ExampleInstrumentedTest.kt b/app/src/androidTest/java/io/github/chinosk/gakumas/localify/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..d695200
--- /dev/null
+++ b/app/src/androidTest/java/io/github/chinosk/gakumas/localify/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package io.github.chinosk.gakumas.localify
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("io.github.chinosk.gakumas.localify", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a578608
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/assets/gakumas-local b/app/src/main/assets/gakumas-local
new file mode 160000
index 0000000..20b1750
--- /dev/null
+++ b/app/src/main/assets/gakumas-local
@@ -0,0 +1 @@
+Subproject commit 20b17501f347c2d6e8044f4d49be8b5ba0b8d48e
diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init
new file mode 100644
index 0000000..2df42b3
--- /dev/null
+++ b/app/src/main/assets/xposed_init
@@ -0,0 +1 @@
+io.github.chinosk.gakumas.localify.GakumasHookMain
\ No newline at end of file
diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt
new file mode 100644
index 0000000..8a3c395
--- /dev/null
+++ b/app/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,61 @@
+
+# For more information about using CMake with Android Studio, read the
+# documentation: https://d.android.com/studio/projects/add-native-code.html.
+# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
+
+# Sets the minimum CMake version required for this project.
+cmake_minimum_required(VERSION 3.22.1)
+
+# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
+# Since this is the top level CMakeLists.txt, the project name is also accessible
+# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
+# build script scope).
+project(MarryKotone)
+
+add_compile_options(-fdeclspec)
+add_compile_options(-Wno-undefined-bool-conversion)
+set(CMAKE_CXX_STANDARD 20)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20")
+
+find_package(xdl REQUIRED CONFIG)
+find_package(shadowhook REQUIRED CONFIG)
+
+# Creates and names a library, sets it as either STATIC
+# or SHARED, and provides the relative paths to its source code.
+# You can define multiple libraries, and CMake builds them for you.
+# Gradle automatically packages shared libraries with your APK.
+#
+# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
+# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
+# is preferred for the same purpose.
+#
+# In order to load a library into your app from Java/Kotlin, you must call
+# System.loadLibrary() and pass the name of the library defined here;
+# for GameActivity/NativeActivity derived applications, the same library name must be
+# used in the AndroidManifest.xml file.
+add_library(${CMAKE_PROJECT_NAME} SHARED
+ # List C/C++ source files with relative paths to this CMakeLists.txt.
+ localify.cpp
+ GakumasLocalify/Plugin.cpp
+ GakumasLocalify/Hook.cpp
+ GakumasLocalify/Log.cpp
+ GakumasLocalify/Misc.cpp
+ GakumasLocalify/Local.cpp
+ )
+
+target_link_libraries(${CMAKE_PROJECT_NAME} xdl::xdl)
+target_link_libraries(${CMAKE_PROJECT_NAME} shadowhook::shadowhook)
+
+include_directories(GakumasLocalify)
+include_directories(${CMAKE_SOURCE_DIR}/deps)
+
+# Specifies libraries CMake should link to your target library. You
+# can link libraries from various origins, such as libraries defined in this
+# build script, prebuilt third-party libraries, or Android system libraries.
+target_link_libraries(${CMAKE_PROJECT_NAME}
+ # List libraries link to the target library
+ android
+ log)
+
+target_compile_features(${CMAKE_PROJECT_NAME} PRIVATE cxx_std_20)
diff --git a/app/src/main/cpp/GakumasLocalify/Hook.cpp b/app/src/main/cpp/GakumasLocalify/Hook.cpp
new file mode 100644
index 0000000..49a6c83
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Hook.cpp
@@ -0,0 +1,322 @@
+#include
+#include "Hook.h"
+#include "Plugin.h"
+#include "Log.h"
+#include "../deps/UnityResolve/UnityResolve.hpp"
+#include "Il2cppUtils.hpp"
+#include "Local.h"
+#include
+
+
+#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(addr); \
+ if (addr) { \
+ hookInstaller->InstallHook(reinterpret_cast(addr), \
+ reinterpret_cast(name##_Hook), \
+ reinterpret_cast(&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();*/
+ 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();
+ ctor_mtd->Invoke(klassInstance);
+ return toString_mtd->Invoke(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 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(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(
+ 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(fontCache)) {
+ return fontCache;
+ }
+ }
+
+ const auto newFont = Font_klass->New();
+ Font_ctor->Invoke(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 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(TMP_Text_this);
+ auto newFont = GetReplaceFont();
+ if (fontAsset && newFont) {
+ set_sourceFontFile->Invoke(fontAsset, newFont);
+ if (!updatedFontPtrs.contains(fontAsset)) {
+ updatedFontPtrs.emplace(fontAsset);
+ UpdateFontAssetData->Invoke(fontAsset);
+ }
+ }
+ set_font->Invoke(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(_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("get_text");
+ const auto set_Text_method = TMP_Text_klass->Get("set_text");
+ const auto currText = get_Text_method->Invoke(_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(_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(_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(
+ "il2cpp_class_get_method_from_name", onComplete_klass, "Invoke", 2);
+ if (onComplete_invoke_mtd) {
+ const auto onComplete_invoke = reinterpret_cast(
+ 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", "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");
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/cpp/GakumasLocalify/Hook.h b/app/src/main/cpp/GakumasLocalify/Hook.h
new file mode 100644
index 0000000..0671560
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Hook.h
@@ -0,0 +1,11 @@
+#ifndef GAKUMAS_LOCALIFY_HOOK_H
+#define GAKUMAS_LOCALIFY_HOOK_H
+
+#include
+
+namespace GakumasLocal::Hook
+{
+ void Install();
+}
+
+#endif //GAKUMAS_LOCALIFY_HOOK_H
diff --git a/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp b/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp
new file mode 100644
index 0000000..182a216
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Il2cppUtils.hpp
@@ -0,0 +1,144 @@
+#pragma once
+#include "../deps/UnityResolve/UnityResolve.hpp"
+#include "Log.h"
+#include
+
+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("il2cpp_domain_get");
+ UnityResolve::Invoke("il2cpp_thread_attach", domain);
+ auto image = UnityResolve::Invoke("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("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("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& 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(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& 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("il2cpp_resolve_icall", s);
+ }
+
+ Il2CppClassHead* get_class_from_instance(const void* instance) {
+ return static_cast(*static_cast(std::assume_aligned(instance)));
+ }
+
+
+ void* find_nested_class(void* klass, std::predicate auto&& predicate)
+ {
+ void* iter{};
+ while (const auto curNestedClass = UnityResolve::Invoke("il2cpp_class_get_nested_types", klass, &iter))
+ {
+ if (static_cast(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(nestedClass)->name == name;
+ });
+ }
+
+}
diff --git a/app/src/main/cpp/GakumasLocalify/Local.cpp b/app/src/main/cpp/GakumasLocalify/Local.cpp
new file mode 100644
index 0000000..c48014d
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Local.cpp
@@ -0,0 +1,174 @@
+#include "Local.h"
+#include "Log.h"
+#include "Plugin.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+namespace GakumasLocal::Local {
+ std::unordered_map i18nData{};
+ std::unordered_map i18nDumpData{};
+ std::unordered_map genericText{};
+ std::unordered_map genericTextDumpData{};
+ std::unordered_set translatedText{};
+
+ std::filesystem::path GetBasePath() {
+ return Plugin::GetInstance().GetHookInstaller()->localizationFilesDir;
+ }
+
+ void LoadJsonDataToMap(const std::filesystem::path& filePath, std::unordered_map& 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(file)), std::istreambuf_iterator());
+ 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& 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(dumpLrcFile)), std::istreambuf_iterator());
+ 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(file)),
+ (std::istreambuf_iterator()));
+ 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;
+
+ }
+
+}
diff --git a/app/src/main/cpp/GakumasLocalify/Local.h b/app/src/main/cpp/GakumasLocalify/Local.h
new file mode 100644
index 0000000..753057a
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Local.h
@@ -0,0 +1,17 @@
+#ifndef GAKUMAS_LOCALIFY_LOCAL_H
+#define GAKUMAS_LOCALIFY_LOCAL_H
+
+#include
+#include
+
+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
diff --git a/app/src/main/cpp/GakumasLocalify/Log.cpp b/app/src/main/cpp/GakumasLocalify/Log.cpp
new file mode 100644
index 0000000..6b53ef8
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Log.cpp
@@ -0,0 +1,142 @@
+#include "Log.h"
+#include
+#include
+#include
+
+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());
+ }
+}
diff --git a/app/src/main/cpp/GakumasLocalify/Log.h b/app/src/main/cpp/GakumasLocalify/Log.h
new file mode 100644
index 0000000..035df2e
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Log.h
@@ -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
diff --git a/app/src/main/cpp/GakumasLocalify/Misc.cpp b/app/src/main/cpp/GakumasLocalify/Misc.cpp
new file mode 100644
index 0000000..99909bf
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Misc.cpp
@@ -0,0 +1,17 @@
+#include "Misc.h"
+
+#include
+#include
+
+
+namespace GakumasLocal::Misc {
+ std::u16string ToUTF16(const std::string_view& str) {
+ std::wstring_convert, char16_t> utf16conv;
+ return utf16conv.from_bytes(str.data(), str.data() + str.size());
+ }
+
+ std::string ToUTF8(const std::u16string_view& str) {
+ std::wstring_convert, char16_t> utf16conv;
+ return utf16conv.to_bytes(str.data(), str.data() + str.size());
+ }
+} // namespace UmaPyogin::Misc
diff --git a/app/src/main/cpp/GakumasLocalify/Misc.h b/app/src/main/cpp/GakumasLocalify/Misc.h
new file mode 100644
index 0000000..073062d
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Misc.h
@@ -0,0 +1,16 @@
+#ifndef GAKUMAS_LOCALIFY_MISC_H
+#define GAKUMAS_LOCALIFY_MISC_H
+
+#include
+#include
+
+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
diff --git a/app/src/main/cpp/GakumasLocalify/Plugin.cpp b/app/src/main/cpp/GakumasLocalify/Plugin.cpp
new file mode 100644
index 0000000..6e1a49f
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Plugin.cpp
@@ -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)
+ {
+ m_HookInstaller = std::move(hookInstaller);
+ Hook::Install();
+ }
+
+ HookInstaller* Plugin::GetHookInstaller() const
+ {
+ return m_HookInstaller.get();
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/cpp/GakumasLocalify/Plugin.h b/app/src/main/cpp/GakumasLocalify/Plugin.h
new file mode 100644
index 0000000..1ac6dab
--- /dev/null
+++ b/app/src/main/cpp/GakumasLocalify/Plugin.h
@@ -0,0 +1,39 @@
+#ifndef GAKUMAS_LOCALIFY_PLUGIN_H
+#define GAKUMAS_LOCALIFY_PLUGIN_H
+
+#include "Misc.h"
+#include
+#include
+
+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* GetHookInstaller() const;
+
+ Plugin(Plugin const&) = delete;
+ Plugin& operator=(Plugin const&) = delete;
+
+ private:
+ Plugin() = default;
+
+ std::unique_ptr m_HookInstaller;
+ };
+
+}
+
+#endif //GAKUMAS_LOCALIFY_PLUGIN_H
diff --git a/app/src/main/cpp/deps/UnityResolve/.gitignore b/app/src/main/cpp/deps/UnityResolve/.gitignore
new file mode 100644
index 0000000..8a30d25
--- /dev/null
+++ b/app/src/main/cpp/deps/UnityResolve/.gitignore
@@ -0,0 +1,398 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio 6 auto-generated project file (contains which files were open etc.)
+*.vbp
+
+# Visual Studio 6 workspace and project file (working project files containing files to include in project)
+*.dsw
+*.dsp
+
+# Visual Studio 6 technical files
+*.ncb
+*.aps
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# Visual Studio History (VSHistory) files
+.vshistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+*.sln.iml
diff --git a/app/src/main/cpp/deps/UnityResolve/LICENSE b/app/src/main/cpp/deps/UnityResolve/LICENSE
new file mode 100644
index 0000000..d3185a6
--- /dev/null
+++ b/app/src/main/cpp/deps/UnityResolve/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 遂沫
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/app/src/main/cpp/deps/UnityResolve/README.md b/app/src/main/cpp/deps/UnityResolve/README.md
new file mode 100644
index 0000000..b6388d2
--- /dev/null
+++ b/app/src/main/cpp/deps/UnityResolve/README.md
@@ -0,0 +1,194 @@
+> [!NOTE]\
+> 有新的功能建议或者Bug可以提交Issues (当然你也可以尝试自己修改代码后提交到该仓库\
+> New feature suggestions or bugs can be commit as issues. Of course, you can also try modifying the code yourself and then commit it to the repository.
+> > Dome
+> > - [Phasmophobia Cheat](https://github.com/issuimo/PhasmophobiaCheat/tree/main)
+
+> 如果是MSVC编译器请打开SEH选项 \
+> If using the MSVC compiler, please open the SEH option. \
+> 对于高版本安卓程序崩溃的可能问题请参阅 [link](https://github.com/issuimo/UnityResolve.hpp/issues/11) \
+> For potential issues related to crashes in higher version Android programs, please refer to the link [link](https://github.com/issuimo/UnityResolve.hpp/issues/11)
+
+简要概述 (Brief overview)
+
+
+# UnityResolve.hpp
+> ### 支持的平台 (Supported platforms)
+> - [X] Windows
+> - [X] Android
+> - [X] Linux
+> ### 类型 (Type)
+> - [X] Camera
+> - [X] Transform
+> - [X] Component
+> - [X] Object (Unity)
+> - [X] LayerMask
+> - [X] Rigidbody
+> - [x] MonoBehaviour
+> - [x] Renderer
+> - [x] Mesh
+> - [X] Behaviour
+> - [X] Physics
+> - [X] GameObject
+> - [X] Collider
+> - [X] Vector4
+> - [X] Vector3
+> - [X] Vector2
+> - [X] Quaternion
+> - [X] Bounds
+> - [X] Plane
+> - [X] Ray
+> - [X] Rect
+> - [X] Color
+> - [X] Matrix4x4
+> - [X] Array
+> - [x] String
+> - [x] Object (C#)
+> - [X] Type (C#)
+> - [X] List
+> - [X] Dictionary
+> - [X] Animator
+> - [X] CapsuleCollider
+> - [X] BoxCollider
+> - [X] Time
+> - More...
+> ### 功能 (Function)
+> - [X] DumpToFile
+> - [X] 附加线程 (Thread Attach / Detach)
+> - [X] 修改静态变量值 (Modifying the value of a static variable)
+> - [X] 获取对象 (Obtaining an instance)
+> - [X] 创建C#字符串 (Create C# String)
+> - [X] 创建C#数组 (Create C# Array)
+> - [X] 创建C#对象 (Create C# instance)
+> - [X] 世界坐标转屏幕坐标/屏幕坐标转世界坐标 (WorldToScreenPoint/ScreenToWorldPoint)
+> - [X] 获取继承子类的名称 (Get the name of the inherited subclass)
+> - [X] 获取函数地址(变量偏移) 及调用(修改/获取) (Get the function address (variable offset) and invoke (modify/get))
+> - [x] 获取Gameobject组件 (Get GameObject component)
+> - More...
+
+功能使用 (How to use)
+
+
+#### 更改平台 (Change platform)
+> ``` c++
+> #define WINDOWS_MODE 1 // 如果需要请改为 1 | 1 if you need
+> #define ANDROID_MODE 0
+> #define LINUX_MODE 0
+> ```
+
+#### 初始化 (Initialization)
+> ``` c++
+> UnityResolve::Init(GetModuleHandle(L"GameAssembly.dll | mono.dll"), UnityResolve::Mode::Mono);
+> // Linux or Android
+> UnityResolve::Init(dlopen(L"GameAssembly.so | mono.so", RTLD_NOW), UnityResolve::Mode::Mono);
+> ```
+> 参数1: dll句柄 \
+> Parameter 1: DLL handle \
+> 参数2: 使用模式 \
+> Parameter 2: Usage mode
+> - Mode::Il2cpp
+> - Mode::Mono
+
+#### 附加线程 (Thread Attach / Detach)
+> ``` c++
+> // C# GC Attach
+> UnityResolve::ThreadAttach();
+>
+> // C# GC Detach
+> UnityResolve::ThreadDetach();
+> ```
+
+#### 获取函数地址(变量偏移) 及调用(修改/获取) (Get the function address (variable offset) and invoke (modify/get))
+> ``` c++
+> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll");
+> const auto pClass = assembly->Get("className | 类名称");
+> // assembly->Get("className | 类名称", "*");
+> // assembly->Get("className | 类名称", "namespace | 空间命名");
+>
+> const auto field = pClass->Get("Field Name | 变量名");
+> const auto fieldOffset = pClass->Get("Field Name | 变量名");
+> const int time = pClass->GetValue(obj Instance | 对象地址, "time");
+> // pClass->GetValue(obj Instance*, name);
+> = pClass->SetValue(obj Instance | 对象地址, "time", 114514);
+> // pClass->SetValue(obj Instance*, name, value);
+> const auto method = pClass->Get("Method Name | 函数名");
+> // pClass->Get("Method Name | 函数名", { "System.String" });
+> // pClass->Get("Method Name | 函数名", { "*", "System.String" });
+> // pClass->Get("Method Name | 函数名", { "*", "", "System.String" });
+> // pClass->Get("Method Name | 函数名", { "*", "System.Int32", "System.String" });
+> // pClass->Get("Method Name | 函数名", { "*", "System.Int32", "System.String", "*" });
+> // "*" == ""
+>
+> const auto functionPtr = method->function;
+>
+> const auto method1 = pClass->Get("method name1 | 函数名称1");
+> const auto method2 = pClass->Get("method name2 | 函数名称2");
+>
+> method1->Invoke(114, 514, "114514");
+> // Invoke(args...);
+>
+> const auto ptr = method2->Cast();
+> // Cast(void);
+> ptr(114514, true);
+> ```
+
+#### 转存储到文件 (DumpToFile)
+> ``` C++
+> UnityResolve::DumpToFile("./output/");
+> ```
+
+#### 创建C#字符串 (Create C# String)
+> ``` c++
+> const auto str = UnityResolve::UnityType::String::New("string | 字符串");
+> std::string cppStr = str.ToString();
+> ```
+
+#### 创建C#数组 (Create C# Array)
+> ``` c++
+> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll");
+> const auto pClass = assembly->Get("className | 类名称");
+> const auto array = UnityResolve::UnityType::Array::New(pClass, size);
+> std::vector cppVector = array.ToVector();
+> ```
+
+#### 创建C#对象 (Create C# instance)
+> ``` c++
+> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll");
+> const auto pClass = assembly->Get("className | 类名称");
+> const auto pGame = pClass->New();
+> ```
+
+#### 获取对象 (Obtaining an instance)
+> ``` c++
+> const auto assembly = UnityResolve::Get("assembly.dll | 程序集名称.dll");
+> const auto pClass = assembly->Get("className | 类名称");
+> std::vector playerVector = pClass->FindObjectsByType();
+> // FindObjectsByType(void);
+> playerVector.size();
+> ```
+
+#### 世界坐标转屏幕坐标/屏幕坐标转世界坐标 (WorldToScreenPoint/ScreenToWorldPoint)
+> ``` c++
+> Camera* pCamera = UnityResolve::UnityType::Camera::GetMain();
+> Vector3 point = pCamera->WorldToScreenPoint(Vector3, Eye::Left);
+> Vector3 world = pCamera->ScreenToWorldPoint(point, Eye::Left);
+> ```
+
+#### 获取继承子类的名称 (Get the name of the inherited subclass)
+> ``` c++
+> const auto assembly = UnityResolve::Get("UnityEngine.CoreModule.dll");
+> const auto pClass = assembly->Get("MonoBehaviour");
+> Parent* pParent = pClass->FindObjectsByType()[0];
+> std::string child = pParent->GetType()->GetFullName();
+> ```
+
+#### 获取Gameobject组件 (Get GameObject component)
+> ``` c++
+> std::vector objs = gameobj->GetComponents(UnityResolve::Get("assembly.dll")->Get("class")));
+> // gameobj->GetComponents(Class* component)
+> std::vector objs = gameobj->GetComponentsInChildren(UnityResolve::Get("assembly.dll")->Get("class")));
+> // gameobj->GetComponentsInChildren(Class* component)
+> std::vector objs = gameobj->GetComponentsInParent(UnityResolve::Get("assembly.dll")->Get("class")));
+> // gameobj->GetComponentsInParent(Class* component)
+> ```
+
diff --git a/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp b/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp
new file mode 100644
index 0000000..2ef7b8f
--- /dev/null
+++ b/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp
@@ -0,0 +1,2474 @@
+/*
+ * Update: 2024-3-2 22:11
+ * Source: https://github.com/issuimo/UnityResolve.hpp
+ * Author: github@issuimo
+ */
+
+#ifndef UNITYRESOLVE_HPP
+#define UNITYRESOLVE_HPP
+#define WINDOWS_MODE 0 // 如果需要请改为 1 | 1 if you need
+#define ANDROID_MODE 1
+#define LINUX_MODE 0
+ /* Never
+ * #define MAC_MODE 0
+ * #define IOS_MODE 0
+ */
+#if WINDOWS_MODE || LINUX_MODE
+#include
+#endif
+#include
+#include
+#include
+#include
+//#include
+#include
+#include
+#include
+#include
+
+#if WINDOWS_MODE
+#include
+#undef GetObject
+#endif
+
+#if WINDOWS_MODE
+#ifdef _WIN64
+#define UNITY_CALLING_CONVENTION __fastcall
+#elif _WIN32
+#define UNITY_CALLING_CONVENTION __cdecl
+#endif
+#elif ANDROID_MODE || LINUX_MODE
+#include
+// #include
+#define UNITY_CALLING_CONVENTION
+#endif
+
+#include "xdl.h"
+#include "../../GakumasLocalify/Log.h"
+
+class UnityResolve final {
+public:
+ struct Assembly;
+ struct Type;
+ struct Class;
+ struct Field;
+ struct Method;
+ class UnityType;
+
+ enum class Mode : char {
+ Il2Cpp,
+ Mono,
+ };
+
+ struct Assembly final {
+ void* address;
+ std::string name;
+ std::string file;
+ std::vector classes;
+
+ [[nodiscard]] auto Get(const std::string& strClass, const std::string& strNamespace = "*", const std::string& strParent = "*") const -> Class* {
+ if (!this) return nullptr;
+ for (const auto pClass : classes) if (strClass == pClass->name && (strNamespace == "*" || pClass->namespaze == strNamespace) && (strParent == "*" || pClass->parent == strParent)) return pClass;
+ return nullptr;
+ }
+ };
+
+ struct Type final {
+ void* address;
+ std::string name;
+ int size;
+
+ // UnityType::CsType*
+ [[nodiscard]] auto GetCSType() const -> void* {
+ if (mode_ == Mode::Il2Cpp) return Invoke("il2cpp_type_get_object", address);
+ return Invoke("mono_type_get_object", pDomain, address);
+ }
+ };
+
+ struct Class final {
+ void* address;
+ std::string name;
+ std::string parent;
+ std::string namespaze;
+ std::vector fields;
+ std::vector methods;
+ void* objType;
+
+ template
+ auto Get(const std::string& name, const std::vector& args = {}) -> RType* {
+ if (!this) return nullptr;
+ if constexpr (std::is_same_v) for (auto pField : fields) if (pField->name == name) return static_cast(pField);
+ if constexpr (std::is_same_v) for (const auto pField : fields) if (pField->name == name) return reinterpret_cast(pField->offset);
+ if constexpr (std::is_same_v) {
+ for (auto pMethod : methods) {
+ if (pMethod->name == name) {
+ if (pMethod->args.empty() && args.empty()) return static_cast(pMethod);
+ if (pMethod->args.size() == args.size()) {
+ size_t index{ 0 };
+ for (size_t i{ 0 }; const auto & typeName : args) if (typeName == "*" || typeName.empty() ? true : pMethod->args[i++]->pType->name == typeName) index++;
+ if (index == pMethod->args.size()) return static_cast(pMethod);
+ }
+ }
+ }
+
+ for (auto pMethod : methods) if (pMethod->name == name) return static_cast(pMethod);
+ }
+ return nullptr;
+ }
+
+ template
+ auto GetValue(void* obj, const std::string& name) -> RType { return *reinterpret_cast(reinterpret_cast(obj) + Get(name)->offset); }
+
+ template
+ auto SetValue(void* obj, const std::string& name, RType value) -> void { return *reinterpret_cast(reinterpret_cast(obj) + Get(name)->offset) = value; }
+
+ // UnityType::CsType*
+ [[nodiscard]] auto GetType() -> void* {
+ if (objType) return objType;
+ if (mode_ == Mode::Il2Cpp) {
+ const auto pUType = Invoke("il2cpp_class_get_type", address);
+ objType = Invoke("il2cpp_type_get_object", pUType);
+ return objType;
+ }
+ const auto pUType = Invoke("mono_class_get_type", address);
+ objType = Invoke("mono_type_get_object", pDomain, pUType);
+ return objType;
+ }
+
+ /**
+ * \brief 获取类所有实例
+ * \tparam T 返回数组类型
+ * \param type 类
+ * \return 返回实例指针数组
+ */
+ template
+ auto FindObjectsByType() -> std::vector {
+ static Method* pMethod;
+
+ if (!pMethod) pMethod = UnityResolve::Get("UnityEngine.CoreModule.dll")->Get("Object")->Get(mode_ == Mode::Il2Cpp ? "FindObjectsOfType" : "FindObjectsOfTypeAll", { "System.Type" });
+ if (!objType) objType = this->GetType();
+
+ if (pMethod && objType) if (auto array = pMethod->Invoke*>(objType)) return array->ToVector();
+
+ return std::vector(0);
+ }
+
+ template
+ auto New() -> T* {
+ if (mode_ == Mode::Il2Cpp) return Invoke("il2cpp_object_new", address);
+ return Invoke("mono_object_new", pDomain, address);
+ }
+ };
+
+ struct Field final {
+ void* address;
+ std::string name;
+ Type* type;
+ Class* klass;
+ std::int32_t offset; // If offset is -1, then it's thread static
+ bool static_field;
+ void* vTable;
+
+ template
+ auto SetValue(T* value) const -> void {
+ if (!static_field) return;
+ if (mode_ == Mode::Il2Cpp) return Invoke("il2cpp_field_static_set_value", address, value);
+ }
+
+ template
+ auto GetValue(T* value) const -> void {
+ if (!static_field) return;
+ if (mode_ == Mode::Il2Cpp) return Invoke("il2cpp_field_static_get_value", address, value);
+ }
+ };
+
+ struct Method final {
+ void* address;
+ std::string name;
+ Class* klass;
+ Type* return_type;
+ std::int32_t flags;
+ bool static_function;
+ void* function;
+
+ struct Arg {
+ std::string name;
+ Type* pType;
+ };
+
+ std::vector args;
+
+ private:
+ bool badPtr{ false };
+
+ public:
+ template
+ auto Invoke(Args... args) -> Return {
+ if (!this) return Return();
+ Compile();
+#if WINDOWS_MODE
+ try {
+ if (!badPtr) badPtr = !IsBadCodePtr(reinterpret_cast(function));
+ if (function && badPtr) return reinterpret_cast(function)(args...);
+ } catch (...) { std::cout << name << " Invoke Error\n"; }
+#else
+ try {
+ if (function) return reinterpret_cast(function)(args...);
+ } catch (...) { std::cout << name << " Invoke Error\n"; }
+#endif
+ return Return();
+ }
+
+ auto Compile() -> void {
+ if (!this) return;
+ if (address && !function && mode_ == Mode::Mono) function = UnityResolve::Invoke("mono_compile_method", address);
+ }
+
+ template
+ auto RuntimeInvoke(Obj* obj, Args... args) -> Return {
+ if (!this) return Return();
+ void* exc{};
+ void* argArray[sizeof...(Args) + 1];
+ if (sizeof...(Args) > 0) {
+ size_t index = 0;
+ ((argArray[index++] = static_cast(&args)), ...);
+ }
+
+ if (mode_ == Mode::Il2Cpp) {
+ if constexpr (std::is_void_v) {
+ UnityResolve::Invoke("il2cpp_runtime_invoke", address, obj, argArray, exc);
+ return;
+ }
+ else return *static_cast(UnityResolve::Invoke("il2cpp_runtime_invoke", address, obj, argArray, exc));
+ }
+
+ if constexpr (std::is_void_v) {
+ UnityResolve::Invoke("mono_runtime_invoke", address, obj, argArray, exc);
+ return;
+ }
+ else return *static_cast(UnityResolve::Invoke("mono_runtime_invoke", address, obj, argArray, exc));
+ return Return();
+ }
+
+ template
+ using MethodPointer = Return(UNITY_CALLING_CONVENTION*)(Args...);
+
+ template
+ auto Cast() -> MethodPointer {
+ if (!this) return nullptr;
+ Compile();
+ if (function) return reinterpret_cast>(function);
+ return nullptr;
+ }
+ };
+
+ static auto ThreadAttach() -> void {
+ if (mode_ == Mode::Il2Cpp) Invoke("il2cpp_thread_attach", pDomain);
+ else {
+ Invoke("mono_thread_attach", pDomain);
+ Invoke("mono_jit_thread_attach", pDomain);
+ }
+ }
+
+ static auto ThreadDetach() -> void {
+ if (mode_ == Mode::Il2Cpp) Invoke("il2cpp_thread_detach", pDomain);
+ else {
+ Invoke("mono_thread_detach", pDomain);
+ Invoke("mono_jit_thread_detach", pDomain);
+ }
+ }
+
+ static auto Init(void* hmodule, const Mode mode = Mode::Mono) -> void {
+ mode_ = mode;
+ hmodule_ = hmodule;
+
+ if (mode_ == Mode::Il2Cpp) {
+ pDomain = Invoke("il2cpp_domain_get");
+ Invoke("il2cpp_thread_attach", pDomain);
+ ForeachAssembly();
+ }
+ else {
+ pDomain = Invoke("mono_get_root_domain");
+ Invoke("mono_thread_attach", pDomain);
+ Invoke("mono_jit_thread_attach", pDomain);
+
+ ForeachAssembly();
+
+ if (Get("UnityEngine.dll") && (!Get("UnityEngine.CoreModule.dll") || !Get("UnityEngine.PhysicsModule.dll"))) {
+ // 兼容某些游戏 (如生死狙击2)
+ for (const std::vector names = { "UnityEngine.CoreModule.dll", "UnityEngine.PhysicsModule.dll" }; const auto name : names) {
+ const auto ass = Get("UnityEngine.dll");
+ const auto assembly = new Assembly{ .address = ass->address, .name = name, .file = ass->file, .classes = ass->classes };
+ UnityResolve::assembly.push_back(assembly);
+ }
+ }
+ }
+ }
+
+#if WINDOWS_MODE || LINUX_MODE /*__cplusplus >= 202002L*/
+ static auto DumpToFile(const std::string& path) -> void {
+ std::ofstream io(path + "dump.cs", std::fstream::out);
+ if (!io) return;
+
+ for (const auto& pAssembly : assembly) {
+ for (const auto& pClass : pAssembly->classes) {
+ io << std::format("\tnamespace: {}", pClass->namespaze.empty() ? "" : pClass->namespaze);
+ io << "\n";
+ io << std::format("\tAssembly: {}\n", pAssembly->name.empty() ? "" : pAssembly->name);
+ io << std::format("\tAssemblyFile: {} \n", pAssembly->file.empty() ? "" : pAssembly->file);
+ io << std::format("\tclass {}{} ", pClass->name, pClass->parent.empty() ? "" : " : " + pClass->parent);
+ io << "{\n\n";
+ for (const auto& pField : pClass->fields) io << std::format("\t\t{:+#06X} | {}{} {};\n", pField->offset, pField->static_field ? "static " : "", pField->type->name, pField->name);
+ io << "\n";
+ for (const auto& pMethod : pClass->methods) {
+ io << std::format("\t\t[Flags: {:032b}] [ParamsCount: {:04d}] |RVA: {:+#010X}|\n", pMethod->flags, pMethod->args.size(), reinterpret_cast(pMethod->function) - reinterpret_cast(hmodule_));
+ io << std::format("\t\t{}{} {}(", pMethod->static_function ? "static " : "", pMethod->return_type->name, pMethod->name);
+ std::string params{};
+ for (const auto& pArg : pMethod->args) params += std::format("{} {}, ", pArg->pType->name, pArg->name);
+ if (!params.empty()) {
+ params.pop_back();
+ params.pop_back();
+ }
+ io << (params.empty() ? "" : params) << ");\n\n";
+ }
+ io << "\t}\n\n";
+ }
+ }
+
+ io << '\n';
+ io.close();
+
+ std::ofstream io2(path + "struct.hpp", std::fstream::out);
+ if (!io2) return;
+
+ for (const auto& pAssembly : assembly) {
+ for (const auto& pClass : pAssembly->classes) {
+ io2 << std::format("\tnamespace: {}", pClass->namespaze.empty() ? "" : pClass->namespaze);
+ io2 << "\n";
+ io2 << std::format("\tAssembly: {}\n", pAssembly->name.empty() ? "" : pAssembly->name);
+ io2 << std::format("\tAssemblyFile: {} \n", pAssembly->file.empty() ? "" : pAssembly->file);
+ io2 << std::format("\tstruct {}{} ", pClass->name, pClass->parent.empty() ? "" : " : " + pClass->parent);
+ io2 << "{\n\n";
+
+ for (size_t i = 0; i < pClass->fields.size(); i++) {
+ if (pClass->fields[i]->static_field) continue;
+
+ auto field = pClass->fields[i];
+
+ next: if ((i + 1) >= pClass->fields.size()) {
+ io2 << std::format("\t\tchar {}[0x{:06X}];\n", field->name, 0x4);
+ continue;
+ }
+
+ if (pClass->fields[i + 1]->static_field) {
+ i++;
+ goto next;
+ }
+
+ std::string name = field->name;
+ std::ranges::replace(name, '<', '_');
+ std::ranges::replace(name, '>', '_');
+
+ if (field->type->name == "System.Int64") {
+ io2 << std::format("\t\tstd::int64_t {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > 8) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - 8);
+ continue;
+ }
+
+ if (field->type->name == "System.UInt64") {
+ io2 << std::format("\t\tstd::uint64_t {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > 8) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - 8);
+ continue;
+ }
+
+ if (field->type->name == "System.Int32") {
+ io2 << std::format("\t\tint {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > 4) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - 4);
+ continue;
+ }
+
+ if (field->type->name == "System.UInt32") {
+ io2 << std::format("\t\tstd::uint32_t {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > 4) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - 4);
+ continue;
+ }
+
+ if (field->type->name == "System.Boolean") {
+ io2 << std::format("\t\tbool {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > 1) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - 1);
+ continue;
+ }
+
+ if (field->type->name == "System.String") {
+ io2 << std::format("\t\tUnityResolve::UnityType::String* {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(void*)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(void*));
+ continue;
+ }
+
+ if (field->type->name == "System.Single") {
+ io2 << std::format("\t\tfloat {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > 4) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - 4);
+ continue;
+ }
+
+ if (field->type->name == "System.Double") {
+ io2 << std::format("\t\tdouble {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > 8) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - 8);
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Vector3") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Vector3 {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(UnityType::Vector3)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(UnityType::Vector3));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Vector2") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Vector2 {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(UnityType::Vector2)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(UnityType::Vector2));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Vector4") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Vector4 {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(UnityType::Vector4)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(UnityType::Vector4));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.GameObject") {
+ io2 << std::format("\t\tUnityResolve::UnityType::GameObject* {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(void*)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(void*));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Transform") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Transform* {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(void*)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(void*));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Animator") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Animator* {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(void*)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(void*));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Physics") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Physics* {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(void*)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(void*));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Component") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Component* {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(void*)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(void*));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Rect") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Rect {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(UnityType::Rect)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(UnityType::Rect));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Quaternion") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Quaternion {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(UnityType::Quaternion)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(UnityType::Quaternion));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Color") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Color {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(UnityType::Color)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(UnityType::Color));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Matrix4x4") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Matrix4x4 {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(UnityType::Matrix4x4)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(UnityType::Matrix4x4));
+ continue;
+ }
+
+ if (field->type->name == "UnityEngine.Rigidbody") {
+ io2 << std::format("\t\tUnityResolve::UnityType::Rigidbody* {};\n", name);
+ if (!pClass->fields[i + 1]->static_field && (pClass->fields[i + 1]->offset - field->offset) > sizeof(void*)) io2 << std::format("\t\tchar {}_[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset - sizeof(void*));
+ continue;
+ }
+
+ io2 << std::format("\t\tchar {}[0x{:06X}];\n", name, pClass->fields[i + 1]->offset - field->offset);
+ }
+
+ io2 << "\n";
+ io2 << "\t};\n\n";
+ }
+ }
+ io2 << '\n';
+ io2.close();
+ }
+#endif
+
+ /**
+ * \brief 调用dll函数
+ * \tparam Return 返回类型 (必须)
+ * \tparam Args 参数类型 (可以忽略)
+ * \param funcName dll导出函数名称
+ * \param args 参数
+ * \return 模板类型
+ */
+ template
+ static auto Invoke(const std::string& funcName, Args... args) -> Return {
+ static std::mutex mutex{};
+ std::lock_guard lock(mutex);
+
+ // 检查函数是否已经获取地址, 没有则自动获取
+#if WINDOWS_MODE
+ if (!address_.contains(funcName) || !address_[funcName]) address_[funcName] = static_cast(GetProcAddress(static_cast(hmodule_), funcName.c_str()));
+#elif ANDROID_MODE || LINUX_MODE
+ if (address_.find(funcName) == address_.end() || !address_[funcName]) {
+ address_[funcName] = xdl_sym(hmodule_, funcName.c_str(), NULL);
+ }
+#endif
+
+ if (address_[funcName] != nullptr) {
+ try {
+ return reinterpret_cast(address_[funcName])(args...);
+ }
+ catch (...) {
+ std::cout << funcName << " Invoke Error\n";
+ Return();
+ }
+ }
+ Return();
+ }
+
+ inline static std::vector assembly;
+
+ static auto Get(const std::string& strAssembly) -> Assembly* {
+ for (const auto pAssembly : assembly) if (pAssembly->name == strAssembly) return pAssembly;
+ return nullptr;
+ }
+
+private:
+ static auto ForeachAssembly() -> void {
+ // 遍历程序集
+ if (mode_ == Mode::Il2Cpp) {
+ size_t nrofassemblies = 0;
+ const auto assemblies = Invoke("il2cpp_domain_get_assemblies", pDomain, &nrofassemblies);
+ for (auto i = 0; i < nrofassemblies; i++) {
+ const auto ptr = assemblies[i];
+ if (ptr == nullptr) continue;
+ auto assembly = new Assembly{ .address = ptr };
+ const auto image = Invoke("il2cpp_assembly_get_image", ptr);
+ assembly->file = Invoke("il2cpp_image_get_filename", image);
+ assembly->name = Invoke("il2cpp_image_get_name", image);
+ UnityResolve::assembly.push_back(assembly);
+ ForeachClass(assembly, image);
+ }
+ }
+ else {
+ Invoke&), std::vector&>("mono_assembly_foreach",
+ [](void* ptr, std::vector& v) {
+ if (ptr == nullptr) return;
+
+ const auto assembly = new Assembly{ .address = ptr, };
+ const auto image = Invoke("mono_assembly_get_image", ptr);
+ assembly->file = Invoke("mono_image_get_filename", image);
+ assembly->name = Invoke("mono_image_get_name", image);
+ assembly->name += ".dll";
+ v.push_back(assembly);
+
+ ForeachClass(assembly, image);
+ },
+ assembly);
+ }
+ }
+
+ static auto ForeachClass(Assembly* assembly, void* image) -> void {
+ // 遍历类
+ if (mode_ == Mode::Il2Cpp) {
+ const auto count = Invoke("il2cpp_image_get_class_count", image);
+ for (auto i = 0; i < count; i++) {
+ const auto pClass = Invoke("il2cpp_image_get_class", image, i);
+ if (pClass == nullptr) continue;
+ const auto pAClass = new Class();
+ pAClass->address = pClass;
+ pAClass->name = Invoke("il2cpp_class_get_name", pClass);
+ if (const auto pPClass = Invoke("il2cpp_class_get_parent", pClass)) pAClass->parent = Invoke("il2cpp_class_get_name", pPClass);
+ pAClass->namespaze = Invoke("il2cpp_class_get_namespace", pClass);
+ assembly->classes.push_back(pAClass);
+
+ ForeachFields(pAClass, pClass);
+ ForeachMethod(pAClass, pClass);
+
+ void* i_class{};
+ void* iter{};
+ do {
+ if ((i_class = Invoke("il2cpp_class_get_interfaces", pClass, &iter))) {
+ ForeachFields(pAClass, i_class);
+ ForeachMethod(pAClass, i_class);
+ }
+ } while (i_class);
+ }
+ }
+ else {
+ const void* table = Invoke("mono_image_get_table_info", image, 2);
+ const auto count = Invoke("mono_table_info_get_rows", table);
+ for (auto i = 0; i < count; i++) {
+ const auto pClass = Invoke("mono_class_get", image, 0x02000000 | (i + 1));
+ if (pClass == nullptr) continue;
+
+ const auto pAClass = new Class();
+ pAClass->address = pClass;
+ pAClass->name = Invoke("mono_class_get_name", pClass);
+ if (const auto pPClass = Invoke("mono_class_get_parent", pClass)) pAClass->parent = Invoke("mono_class_get_name", pPClass);
+ pAClass->namespaze = Invoke("mono_class_get_namespace", pClass);
+ assembly->classes.push_back(pAClass);
+
+ ForeachFields(pAClass, pClass);
+ ForeachMethod(pAClass, pClass);
+
+ void* iClass{};
+ void* iiter{};
+
+ do {
+ if ((iClass = Invoke("mono_class_get_interfaces", pClass, &iiter))) {
+ ForeachFields(pAClass, iClass);
+ ForeachMethod(pAClass, iClass);
+ }
+ } while (iClass);
+ }
+ }
+ }
+
+ static auto ForeachFields(Class* klass, void* pKlass) -> void {
+ // 遍历成员
+ if (mode_ == Mode::Il2Cpp) {
+ void* iter = nullptr;
+ void* field;
+ do {
+ if ((field = Invoke("il2cpp_class_get_fields", pKlass, &iter))) {
+ const auto pField = new Field{ .address = field, .name = Invoke("il2cpp_field_get_name", field), .type = new Type{.address = Invoke("il2cpp_field_get_type", field)}, .klass = klass, .offset = Invoke("il2cpp_field_get_offset", field), .static_field = false, .vTable = nullptr };
+ int tSize{};
+ pField->static_field = pField->offset <= 0;
+ pField->type->name = Invoke("il2cpp_type_get_name", pField->type->address);
+ pField->type->size = -1;
+ klass->fields.push_back(pField);
+ }
+ } while (field);
+ }
+ else {
+ void* iter = nullptr;
+ void* field;
+ do {
+ if ((field = Invoke("mono_class_get_fields", pKlass, &iter))) {
+ const auto pField = new Field{ .address = field, .name = Invoke("mono_field_get_name", field), .type = new Type{.address = Invoke("mono_field_get_type", field)}, .klass = klass, .offset = Invoke("mono_field_get_offset", field), .static_field = false, .vTable = nullptr };
+ int tSize{};
+ pField->static_field = pField->offset <= 0;
+ pField->type->name = Invoke("mono_type_get_name", pField->type->address);
+ pField->type->size = Invoke("mono_type_size", pField->type->address, &tSize);
+ klass->fields.push_back(pField);
+ }
+ } while (field);
+ }
+ }
+
+ static auto ForeachMethod(Class* klass, void* pKlass) -> void {
+ // 遍历方法
+ if (mode_ == Mode::Il2Cpp) {
+ void* iter = nullptr;
+ void* method;
+ do {
+ if ((method = Invoke("il2cpp_class_get_methods", pKlass, &iter))) {
+ int fFlags{};
+ const auto pMethod = new Method{};
+ pMethod->address = method;
+ pMethod->name = Invoke("il2cpp_method_get_name", method);
+ pMethod->klass = klass;
+ pMethod->return_type = new Type{ .address = Invoke("il2cpp_method_get_return_type", method), };
+ pMethod->flags = Invoke("il2cpp_method_get_flags", method, &fFlags);
+
+ int tSize{};
+ pMethod->static_function = pMethod->flags & 0x10;
+ pMethod->return_type->name = Invoke("il2cpp_type_get_name", pMethod->return_type->address);
+ pMethod->return_type->size = -1;
+ pMethod->function = *static_cast(method);
+ klass->methods.push_back(pMethod);
+ const auto argCount = Invoke("il2cpp_method_get_param_count", method);
+ for (auto index = 0; index < argCount; index++) pMethod->args.push_back(new Method::Arg{ Invoke("il2cpp_method_get_param_name", method, index), new Type{.address = Invoke("il2cpp_method_get_param", method, index), .name = Invoke("il2cpp_type_get_name", Invoke("il2cpp_method_get_param", method, index)), .size = -1} });
+ }
+ } while (method);
+ }
+ else {
+ void* iter = nullptr;
+ void* method;
+ do {
+ if ((method = Invoke