forked from chinosk/gkms-local
				
			Add plugin initialization progress (#2)
* add plugin init progress * add lazy initializing * fix: lazy initializing - can't get class from `*` namespace * fix: lazy mode - can't get class with empty namespace * update build.yml - Adapt to Gitea Actions
This commit is contained in:
		
							parent
							
								
									c28e05e271
								
							
						
					
					
						commit
						6ddf4212d4
					
				| 
						 | 
				
			
			@ -12,21 +12,22 @@ jobs:
 | 
			
		|||
      with:
 | 
			
		||||
        submodules: recursive
 | 
			
		||||
 | 
			
		||||
    - name: set up android development environment
 | 
			
		||||
      uses: android-actions/setup-android@v2
 | 
			
		||||
 | 
			
		||||
    - name: install dependencies
 | 
			
		||||
      run: |
 | 
			
		||||
        sdkmanager --install "cmake;3.22.1"
 | 
			
		||||
        echo "cmake.dir=/usr/local/lib/android/sdk/cmake/3.22.1" > local.properties
 | 
			
		||||
        npm install -g pnpm
 | 
			
		||||
 | 
			
		||||
    - name: Setup Java JDK
 | 
			
		||||
      uses: actions/setup-java@v4.2.1
 | 
			
		||||
      with:
 | 
			
		||||
        distribution: 'temurin'
 | 
			
		||||
        java-version: '21'
 | 
			
		||||
 | 
			
		||||
    - name: Setup Android Development Environment
 | 
			
		||||
      uses: android-actions/setup-android@v3
 | 
			
		||||
 | 
			
		||||
    - name: install dependencies
 | 
			
		||||
      run: |
 | 
			
		||||
        sdkmanager --install "cmake;3.22.1"
 | 
			
		||||
        echo "cmake.dir=$ANDROID_HOME/cmake/3.22.1" > local.properties
 | 
			
		||||
        echo "$ANDROID_HOME/build-tools/34.0.0" >> $GITHUB_PATH
 | 
			
		||||
        npm install -g pnpm
 | 
			
		||||
 | 
			
		||||
    - name: Update Submodules
 | 
			
		||||
      run: |
 | 
			
		||||
        git submodule foreach --recursive 'git pull --rebase origin main --allow-unrelated-histories'
 | 
			
		||||
| 
						 | 
				
			
			@ -58,24 +59,44 @@ jobs:
 | 
			
		|||
      run: ./gradlew build
 | 
			
		||||
 | 
			
		||||
    - uses: actions/upload-artifact@v4
 | 
			
		||||
      id: upload_unsigned_v4
 | 
			
		||||
      with:
 | 
			
		||||
        name: GakumasLocalify-Unsigned-apk
 | 
			
		||||
        path: app/build/outputs/apk/debug/app-debug.apk
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - uses: ilharp/sign-android-release@v1
 | 
			
		||||
    - name: Upload Unsigned APK with v3 if v4 failed
 | 
			
		||||
      if: steps.upload_unsigned_v4.outcome == 'failure'
 | 
			
		||||
      uses: actions/upload-artifact@v3
 | 
			
		||||
      with:
 | 
			
		||||
        name: GakumasLocalify-Unsigned-apk
 | 
			
		||||
        path: app/build/outputs/apk/debug/app-debug.apk
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - uses: r0adkll/sign-android-release@v1
 | 
			
		||||
      name: Sign app APK
 | 
			
		||||
      id: sign_app
 | 
			
		||||
      with:
 | 
			
		||||
        releaseDir: app/build/outputs/apk/debug
 | 
			
		||||
        signingKey: ${{ secrets.KEYSTOREB64 }}
 | 
			
		||||
        keyAlias: ${{ secrets.ANDROID_KEY_ALIAS }}
 | 
			
		||||
        releaseDirectory: app/build/outputs/apk/debug
 | 
			
		||||
        signingKeyBase64: ${{ secrets.KEYSTOREB64 }}
 | 
			
		||||
        alias: ${{ secrets.ANDROID_KEY_ALIAS }}
 | 
			
		||||
        keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
 | 
			
		||||
        keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
 | 
			
		||||
        buildToolsVersion: 33.0.0
 | 
			
		||||
      env:
 | 
			
		||||
        BUILD_TOOLS_VERSION: "34.0.0"
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - uses: actions/upload-artifact@v4
 | 
			
		||||
      id: upload_signed_v4
 | 
			
		||||
      with:
 | 
			
		||||
        name: GakumasLocalify-Signed-apk
 | 
			
		||||
        path: ${{steps.sign_app.outputs.signedFile}}
 | 
			
		||||
        path: ${{steps.sign_app.outputs.signedReleaseFile}}
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
 | 
			
		||||
    - name: Upload Signed APK with v3 if v4 failed
 | 
			
		||||
      if: steps.upload_signed_v4.outcome == 'failure'
 | 
			
		||||
      uses: actions/upload-artifact@v3
 | 
			
		||||
      with:
 | 
			
		||||
        name: GakumasLocalify-Signed-apk
 | 
			
		||||
        path: ${{steps.sign_app.outputs.signedReleaseFile}}
 | 
			
		||||
      continue-on-error: true
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,8 @@ std::unordered_set<void*> hookedStubs{};
 | 
			
		|||
            GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr);                         \
 | 
			
		||||
        }                                                                                          \
 | 
			
		||||
    }                                                                                              \
 | 
			
		||||
    else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr)
 | 
			
		||||
    else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr);                      \
 | 
			
		||||
    if (Config::lazyInit) UnityResolveProgress::classProgress.current++
 | 
			
		||||
 | 
			
		||||
void UnHookAll() {
 | 
			
		||||
    for (const auto i: hookedStubs) {
 | 
			
		||||
| 
						 | 
				
			
			@ -97,7 +98,7 @@ namespace GakumasLocal::HookMain {
 | 
			
		|||
    UnityResolve::UnityType::Transform* cameraTransformCache = nullptr;
 | 
			
		||||
    void CheckAndUpdateMainCamera() {
 | 
			
		||||
        if (!Config::enableFreeCamera) return;
 | 
			
		||||
        if (IsNativeObjectAlive(mainCameraCache)) return;
 | 
			
		||||
        if (IsNativeObjectAlive(mainCameraCache) && IsNativeObjectAlive(cameraTransformCache)) return;
 | 
			
		||||
 | 
			
		||||
        mainCameraCache = UnityResolve::UnityType::Camera::GetMain();
 | 
			
		||||
        cameraTransformCache = mainCameraCache->GetTransform();
 | 
			
		||||
| 
						 | 
				
			
			@ -827,7 +828,8 @@ namespace GakumasLocal::HookMain {
 | 
			
		|||
 | 
			
		||||
    void StartInjectFunctions() {
 | 
			
		||||
        const auto hookInstaller = Plugin::GetInstance().GetHookInstaller();
 | 
			
		||||
        UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW), UnityResolve::Mode::Il2Cpp);
 | 
			
		||||
        UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW),
 | 
			
		||||
                           UnityResolve::Mode::Il2Cpp, Config::lazyInit);
 | 
			
		||||
 | 
			
		||||
        ADD_HOOK(AssetBundle_LoadAssetAsync, Il2cppUtils::il2cpp_resolve_icall(
 | 
			
		||||
                "UnityEngine.AssetBundle::LoadAssetAsync_Internal(System.String,System.Type)"));
 | 
			
		||||
| 
						 | 
				
			
			@ -959,10 +961,30 @@ namespace GakumasLocal::HookMain {
 | 
			
		|||
 | 
			
		||||
        Log::Info("Start init plugin...");
 | 
			
		||||
 | 
			
		||||
        if (Config::lazyInit) {
 | 
			
		||||
            UnityResolveProgress::startInit = true;
 | 
			
		||||
            UnityResolveProgress::assembliesProgress.total = 2;
 | 
			
		||||
            UnityResolveProgress::assembliesProgress.current = 1;
 | 
			
		||||
            UnityResolveProgress::classProgress.total = 36;
 | 
			
		||||
            UnityResolveProgress::classProgress.current = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        StartInjectFunctions();
 | 
			
		||||
        GKCamera::initCameraSettings();
 | 
			
		||||
 | 
			
		||||
        if (Config::lazyInit) {
 | 
			
		||||
            UnityResolveProgress::assembliesProgress.current = 2;
 | 
			
		||||
            UnityResolveProgress::classProgress.total = 1;
 | 
			
		||||
            UnityResolveProgress::classProgress.current = 0;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Local::LoadData();
 | 
			
		||||
 | 
			
		||||
        if (Config::lazyInit) {
 | 
			
		||||
            UnityResolveProgress::classProgress.current = 1;
 | 
			
		||||
            UnityResolveProgress::startInit = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Log::Info("Plugin init finished.");
 | 
			
		||||
        return ret;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ namespace GakumasLocal::Config {
 | 
			
		|||
 | 
			
		||||
    bool dbgMode = false;
 | 
			
		||||
    bool enabled = true;
 | 
			
		||||
    bool lazyInit = true;
 | 
			
		||||
    bool replaceFont = true;
 | 
			
		||||
    bool forceExportResource = true;
 | 
			
		||||
    bool textTest = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -55,6 +56,7 @@ namespace GakumasLocal::Config {
 | 
			
		|||
 | 
			
		||||
            GetConfigItem(dbgMode);
 | 
			
		||||
            GetConfigItem(enabled);
 | 
			
		||||
            GetConfigItem(lazyInit);
 | 
			
		||||
            GetConfigItem(replaceFont);
 | 
			
		||||
            GetConfigItem(forceExportResource);
 | 
			
		||||
            GetConfigItem(gameOrientation);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,6 +5,7 @@ namespace GakumasLocal::Config {
 | 
			
		|||
 | 
			
		||||
    extern bool dbgMode;
 | 
			
		||||
    extern bool enabled;
 | 
			
		||||
    extern bool lazyInit;
 | 
			
		||||
    extern bool replaceFont;
 | 
			
		||||
    extern bool forceExportResource;
 | 
			
		||||
    extern int gameOrientation;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,6 +47,18 @@
 | 
			
		|||
#include "../../GakumasLocalify/Log.h"
 | 
			
		||||
#include "../../GakumasLocalify/Misc.hpp"
 | 
			
		||||
 | 
			
		||||
class UnityResolveProgress final {
 | 
			
		||||
public:
 | 
			
		||||
    struct Progress {
 | 
			
		||||
        long current = 0;
 | 
			
		||||
        long total = 1;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    static bool startInit;
 | 
			
		||||
    static Progress assembliesProgress;
 | 
			
		||||
    static Progress classProgress;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class UnityResolve final {
 | 
			
		||||
public:
 | 
			
		||||
	struct Assembly;
 | 
			
		||||
| 
						 | 
				
			
			@ -69,8 +81,16 @@ public:
 | 
			
		|||
 | 
			
		||||
		[[nodiscard]] auto Get(const std::string& strClass, const std::string& strNamespace = "*", const std::string& strParent = "*") const -> Class* {
 | 
			
		||||
			if (!this) return nullptr;
 | 
			
		||||
            /*
 | 
			
		||||
            if (lazyInit_ && classes.empty()) {
 | 
			
		||||
                const auto image = Invoke<void*>("il2cpp_assembly_get_image", address);
 | 
			
		||||
                ForeachClass(const_cast<Assembly *>(this), image);
 | 
			
		||||
            }*/
 | 
			
		||||
			for (const auto pClass : classes) if (strClass == pClass->name && (strNamespace == "*" || pClass->namespaze == strNamespace) && (strParent == "*" || pClass->parent == strParent)) return pClass;
 | 
			
		||||
			return nullptr;
 | 
			
		||||
			if (lazyInit_) {
 | 
			
		||||
                return FillClass_Il2ccpp(const_cast<Assembly *>(this), strNamespace.c_str(), strClass.c_str());
 | 
			
		||||
            }
 | 
			
		||||
            return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -279,14 +299,17 @@ public:
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static auto Init(void* hmodule, const Mode mode = Mode::Mono) -> void {
 | 
			
		||||
	static auto Init(void* hmodule, const Mode mode = Mode::Mono, const bool lazyInit = false) -> void {
 | 
			
		||||
		mode_ = mode;
 | 
			
		||||
		hmodule_ = hmodule;
 | 
			
		||||
        lazyInit_ = lazyInit;
 | 
			
		||||
 | 
			
		||||
		if (mode_ == Mode::Il2Cpp) {
 | 
			
		||||
            if (!lazyInit) UnityResolveProgress::startInit = true;
 | 
			
		||||
			pDomain = Invoke<void*>("il2cpp_domain_get");
 | 
			
		||||
			Invoke<void*>("il2cpp_thread_attach", pDomain);
 | 
			
		||||
			ForeachAssembly();
 | 
			
		||||
            if (!lazyInit) UnityResolveProgress::startInit = false;
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			pDomain = Invoke<void*>("mono_get_root_domain");
 | 
			
		||||
| 
						 | 
				
			
			@ -561,7 +584,11 @@ private:
 | 
			
		|||
		if (mode_ == Mode::Il2Cpp) {
 | 
			
		||||
			size_t     nrofassemblies = 0;
 | 
			
		||||
			const auto assemblies = Invoke<void**>("il2cpp_domain_get_assemblies", pDomain, &nrofassemblies);
 | 
			
		||||
 | 
			
		||||
            if (!lazyInit_) UnityResolveProgress::assembliesProgress.total = nrofassemblies;
 | 
			
		||||
 | 
			
		||||
			for (auto i = 0; i < nrofassemblies; i++) {
 | 
			
		||||
                if (!lazyInit_) UnityResolveProgress::assembliesProgress.current = i + 1;
 | 
			
		||||
				const auto ptr = assemblies[i];
 | 
			
		||||
				if (ptr == nullptr) continue;
 | 
			
		||||
				auto       assembly = new Assembly{ .address = ptr };
 | 
			
		||||
| 
						 | 
				
			
			@ -569,7 +596,9 @@ private:
 | 
			
		|||
				assembly->file = Invoke<const char*>("il2cpp_image_get_filename", image);
 | 
			
		||||
				assembly->name = Invoke<const char*>("il2cpp_image_get_name", image);
 | 
			
		||||
				UnityResolve::assembly.push_back(assembly);
 | 
			
		||||
				ForeachClass(assembly, image);
 | 
			
		||||
                if (!lazyInit_) {
 | 
			
		||||
                    ForeachClass(assembly, image);
 | 
			
		||||
                }
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
| 
						 | 
				
			
			@ -590,11 +619,60 @@ private:
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    static auto GetPClassFromUnknownNamespace(void* image, const char* klassName) -> void* {
 | 
			
		||||
        const auto count = Invoke<int>("il2cpp_image_get_class_count", image);
 | 
			
		||||
        for (auto i = 0; i < count; i++) {
 | 
			
		||||
            const auto pClass = Invoke<void*>("il2cpp_image_get_class", image, i);
 | 
			
		||||
            const auto className = Invoke<const char*>("il2cpp_class_get_name", pClass);
 | 
			
		||||
            if (strcmp(className, klassName) == 0) {
 | 
			
		||||
                return pClass;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return nullptr;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static auto FillClass_Il2ccpp(Assembly* assembly, const char* namespaze, const char* klassName) -> Class* {
 | 
			
		||||
        auto image = Invoke<void*>("il2cpp_assembly_get_image", assembly->address);
 | 
			
		||||
        void* pClass;
 | 
			
		||||
        if (strcmp(namespaze, "*") == 0) {
 | 
			
		||||
            pClass = GetPClassFromUnknownNamespace(image, klassName);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            pClass = Invoke<void*>("il2cpp_class_from_name", image, namespaze, klassName);
 | 
			
		||||
        }
 | 
			
		||||
        if (!pClass && (strlen(namespaze) == 0)) {
 | 
			
		||||
            pClass = GetPClassFromUnknownNamespace(image, klassName);
 | 
			
		||||
        }
 | 
			
		||||
        if (pClass == nullptr) return nullptr;
 | 
			
		||||
        const auto pAClass = new Class();
 | 
			
		||||
        pAClass->address = pClass;
 | 
			
		||||
        pAClass->name = Invoke<const char*>("il2cpp_class_get_name", pClass);
 | 
			
		||||
        if (const auto pPClass = Invoke<void*>("il2cpp_class_get_parent", pClass)) pAClass->parent = Invoke<const char*>("il2cpp_class_get_name", pPClass);
 | 
			
		||||
        // pAClass->namespaze = Invoke<const char*>("il2cpp_class_get_namespace", pClass);
 | 
			
		||||
        pAClass->namespaze = namespaze;
 | 
			
		||||
        assembly->classes.push_back(pAClass);
 | 
			
		||||
 | 
			
		||||
        ForeachFields(pAClass, pClass);
 | 
			
		||||
        ForeachMethod(pAClass, pClass);
 | 
			
		||||
 | 
			
		||||
        void* i_class{};
 | 
			
		||||
        void* iter{};
 | 
			
		||||
        do {
 | 
			
		||||
            if ((i_class = Invoke<void*>("il2cpp_class_get_interfaces", pClass, &iter))) {
 | 
			
		||||
                ForeachFields(pAClass, i_class);
 | 
			
		||||
                ForeachMethod(pAClass, i_class);
 | 
			
		||||
            }
 | 
			
		||||
        } while (i_class);
 | 
			
		||||
        return pAClass;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	static auto ForeachClass(Assembly* assembly, void* image) -> void {
 | 
			
		||||
		// 遍历类
 | 
			
		||||
		if (mode_ == Mode::Il2Cpp) {
 | 
			
		||||
			const auto count = Invoke<int>("il2cpp_image_get_class_count", image);
 | 
			
		||||
            if (!lazyInit_) UnityResolveProgress::classProgress.total = count;
 | 
			
		||||
			for (auto i = 0; i < count; i++) {
 | 
			
		||||
                if (!lazyInit_) UnityResolveProgress::classProgress.current = i + 1;
 | 
			
		||||
				const auto pClass = Invoke<void*>("il2cpp_image_get_class", image, i);
 | 
			
		||||
				if (pClass == nullptr) continue;
 | 
			
		||||
				const auto pAClass = new Class();
 | 
			
		||||
| 
						 | 
				
			
			@ -2586,6 +2664,7 @@ public:
 | 
			
		|||
private:
 | 
			
		||||
	inline static Mode                                   mode_{};
 | 
			
		||||
	inline static void* hmodule_;
 | 
			
		||||
	inline static bool lazyInit_;
 | 
			
		||||
	inline static std::unordered_map<std::string, void*> address_{};
 | 
			
		||||
	inline static void* pDomain{};
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,10 @@ JavaVM* g_javaVM = nullptr;
 | 
			
		|||
jclass g_gakumasHookMainClass = nullptr;
 | 
			
		||||
jmethodID showToastMethodId = nullptr;
 | 
			
		||||
 | 
			
		||||
bool UnityResolveProgress::startInit = false;
 | 
			
		||||
UnityResolveProgress::Progress UnityResolveProgress::assembliesProgress{};
 | 
			
		||||
UnityResolveProgress::Progress UnityResolveProgress::classProgress{};
 | 
			
		||||
 | 
			
		||||
namespace
 | 
			
		||||
{
 | 
			
		||||
    class AndroidHookInstaller : public GakumasLocal::HookInstaller
 | 
			
		||||
| 
						 | 
				
			
			@ -114,8 +118,37 @@ Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env,
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
extern "C"
 | 
			
		||||
JNIEXPORT void JNICALL
 | 
			
		||||
JNIEXPORT jint JNICALL
 | 
			
		||||
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_pluginCallbackLooper(JNIEnv *env,
 | 
			
		||||
                                                                             jclass clazz) {
 | 
			
		||||
    GakumasLocal::Log::ToastLoop(env, clazz);
 | 
			
		||||
 | 
			
		||||
    if (UnityResolveProgress::startInit) {
 | 
			
		||||
        return 9;
 | 
			
		||||
    }
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
extern "C"
 | 
			
		||||
JNIEXPORT void JNICALL
 | 
			
		||||
Java_io_github_chinosk_gakumas_localify_models_NativeInitProgress_pluginInitProgressLooper(
 | 
			
		||||
        JNIEnv *env, jclass clazz, jobject progress) {
 | 
			
		||||
 | 
			
		||||
    // jclass progressClass = env->GetObjectClass(progress);
 | 
			
		||||
 | 
			
		||||
    static jfieldID startInitFieldID = env->GetStaticFieldID(clazz, "startInit", "Z");
 | 
			
		||||
 | 
			
		||||
    static jmethodID setAssembliesProgressDataMethodID = env->GetMethodID(clazz, "setAssembliesProgressData", "(JJ)V");
 | 
			
		||||
    static jmethodID setClassProgressDataMethodID = env->GetMethodID(clazz, "setClassProgressData", "(JJ)V");
 | 
			
		||||
 | 
			
		||||
    // jboolean startInit = env->GetStaticBooleanField(clazz, startInitFieldID);
 | 
			
		||||
 | 
			
		||||
    env->SetStaticBooleanField(clazz, startInitFieldID, UnityResolveProgress::startInit);
 | 
			
		||||
 | 
			
		||||
    env->CallVoidMethod(progress, setAssembliesProgressDataMethodID,
 | 
			
		||||
                        UnityResolveProgress::assembliesProgress.current, UnityResolveProgress::assembliesProgress.total);
 | 
			
		||||
    env->CallVoidMethod(progress, setClassProgressDataMethodID,
 | 
			
		||||
                        UnityResolveProgress::classProgress.current, UnityResolveProgress::classProgress.total);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -17,6 +17,7 @@ interface ConfigListener {
 | 
			
		|||
    fun onForceExportResourceChanged(value: Boolean)
 | 
			
		||||
    fun onTextTestChanged(value: Boolean)
 | 
			
		||||
    fun onReplaceFontChanged(value: Boolean)
 | 
			
		||||
    fun onLazyInitChanged(value: Boolean)
 | 
			
		||||
    fun onEnableFreeCameraChanged(value: Boolean)
 | 
			
		||||
    fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int)
 | 
			
		||||
    fun onUnlockAllLiveChanged(value: Boolean)
 | 
			
		||||
| 
						 | 
				
			
			@ -111,6 +112,11 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
 | 
			
		|||
        pushKeyEvent(KeyEvent(1145, 30))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onLazyInitChanged(value: Boolean) {
 | 
			
		||||
        config.lazyInit = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onTextTestChanged(value: Boolean) {
 | 
			
		||||
        config.textTest = value
 | 
			
		||||
        saveConfig()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,7 +34,9 @@ import java.util.Locale
 | 
			
		|||
import kotlin.system.measureTimeMillis
 | 
			
		||||
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
 | 
			
		||||
import io.github.chinosk.gakumas.localify.mainUtils.json
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.ProgramConfig
 | 
			
		||||
import io.github.chinosk.gakumas.localify.ui.game_attach.InitProgressUI
 | 
			
		||||
 | 
			
		||||
val TAG = "GakumasLocalify"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +51,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
 | 
			
		||||
    private var getConfigError: Exception? = null
 | 
			
		||||
    private var externalFilesChecked: Boolean = false
 | 
			
		||||
    private var gameActivity: Activity? = null
 | 
			
		||||
 | 
			
		||||
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
 | 
			
		||||
//        if (lpparam.packageName == "io.github.chinosk.gakumas.localify") {
 | 
			
		||||
| 
						 | 
				
			
			@ -135,6 +138,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
                super.beforeHookedMethod(param)
 | 
			
		||||
                Log.d(TAG, "onStart")
 | 
			
		||||
                val currActivity = param.thisObject as Activity
 | 
			
		||||
                gameActivity = currActivity
 | 
			
		||||
                if (getConfigError != null) {
 | 
			
		||||
                    showGetConfigFailed(currActivity)
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -148,6 +152,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
            override fun beforeHookedMethod(param: MethodHookParam) {
 | 
			
		||||
                Log.d(TAG, "onResume")
 | 
			
		||||
                val currActivity = param.thisObject as Activity
 | 
			
		||||
                gameActivity = currActivity
 | 
			
		||||
                if (getConfigError != null) {
 | 
			
		||||
                    showGetConfigFailed(currActivity)
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			@ -206,9 +211,30 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
    private fun startLoop() {
 | 
			
		||||
        GlobalScope.launch {
 | 
			
		||||
            val interval = 1000L / 30
 | 
			
		||||
            var lastFrameStartInit = NativeInitProgress.startInit
 | 
			
		||||
            val initProgressUI = InitProgressUI()
 | 
			
		||||
 | 
			
		||||
            while (isActive) {
 | 
			
		||||
                val timeTaken = measureTimeMillis {
 | 
			
		||||
                    pluginCallbackLooper()
 | 
			
		||||
                    val returnValue = pluginCallbackLooper()  // plugin main thread loop
 | 
			
		||||
                    if (returnValue == 9) {
 | 
			
		||||
                        NativeInitProgress.startInit = true
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (NativeInitProgress.startInit) {  // if init, update data
 | 
			
		||||
                        NativeInitProgress.pluginInitProgressLooper(NativeInitProgress)
 | 
			
		||||
                        gameActivity?.let { initProgressUI.updateData(it) }
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if ((gameActivity != null) && (lastFrameStartInit != NativeInitProgress.startInit)) {  // change status
 | 
			
		||||
                        if (NativeInitProgress.startInit) {
 | 
			
		||||
                            initProgressUI.createView(gameActivity!!)
 | 
			
		||||
                        }
 | 
			
		||||
                        else {
 | 
			
		||||
                            initProgressUI.finishLoad(gameActivity!!)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    lastFrameStartInit = NativeInitProgress.startInit
 | 
			
		||||
                }
 | 
			
		||||
                delay(interval - timeTaken)
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -413,7 +439,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        @JvmStatic
 | 
			
		||||
        external fun pluginCallbackLooper()
 | 
			
		||||
        external fun pluginCallbackLooper(): Int
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable
 | 
			
		|||
data class GakumasConfig (
 | 
			
		||||
    var dbgMode: Boolean = false,
 | 
			
		||||
    var enabled: Boolean = true,
 | 
			
		||||
    var lazyInit: Boolean = true,
 | 
			
		||||
    var replaceFont: Boolean = true,
 | 
			
		||||
    var textTest: Boolean = false,
 | 
			
		||||
    var dumpText: Boolean = false,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,25 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.models
 | 
			
		||||
 | 
			
		||||
data class ProgressData(
 | 
			
		||||
    var current: Long = 0,
 | 
			
		||||
    var total: Long = 1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
object NativeInitProgress {
 | 
			
		||||
    var assembliesProgress = ProgressData()
 | 
			
		||||
    var classProgress = ProgressData()
 | 
			
		||||
    var startInit: Boolean = false
 | 
			
		||||
 | 
			
		||||
    fun setAssembliesProgressData(current: Long, total: Long) {
 | 
			
		||||
        assembliesProgress.current = current
 | 
			
		||||
        assembliesProgress.total = total
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun setClassProgressData(current: Long, total: Long) {
 | 
			
		||||
        classProgress.current = current
 | 
			
		||||
        classProgress.total = total
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    external fun pluginInitProgressLooper(progress: NativeInitProgress)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,176 @@
 | 
			
		|||
package io.github.chinosk.gakumas.localify.ui.game_attach
 | 
			
		||||
 | 
			
		||||
import android.annotation.SuppressLint
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.res.ColorStateList
 | 
			
		||||
import android.content.res.Resources
 | 
			
		||||
import android.graphics.Color
 | 
			
		||||
import android.graphics.Typeface
 | 
			
		||||
import android.graphics.drawable.GradientDrawable
 | 
			
		||||
import android.graphics.drawable.LayerDrawable
 | 
			
		||||
import android.graphics.drawable.ShapeDrawable
 | 
			
		||||
import android.graphics.drawable.shapes.RectShape
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.view.Gravity
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.widget.FrameLayout
 | 
			
		||||
import android.widget.ImageButton
 | 
			
		||||
import android.widget.ImageView
 | 
			
		||||
import android.widget.LinearLayout
 | 
			
		||||
import android.widget.ProgressBar
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import io.github.chinosk.gakumas.localify.TAG
 | 
			
		||||
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
 | 
			
		||||
import kotlinx.coroutines.DelicateCoroutinesApi
 | 
			
		||||
import kotlinx.coroutines.GlobalScope
 | 
			
		||||
import kotlinx.coroutines.delay
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InitProgressUI {
 | 
			
		||||
    private var uiCreated = false
 | 
			
		||||
    private lateinit var rootView: ViewGroup
 | 
			
		||||
    private lateinit var container: LinearLayout
 | 
			
		||||
    private lateinit var assembliesProgressBar: ProgressBar
 | 
			
		||||
    private lateinit var classProgressBar: ProgressBar
 | 
			
		||||
    private lateinit var titleText: TextView
 | 
			
		||||
    private lateinit var assembliesProgressText: TextView
 | 
			
		||||
    private lateinit var classProgressText: TextView
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
    fun createView(context: Context) {
 | 
			
		||||
        if (uiCreated) return
 | 
			
		||||
        uiCreated = true
 | 
			
		||||
        val activity = context as? Activity ?: return
 | 
			
		||||
        rootView = activity.findViewById<ViewGroup>(android.R.id.content)
 | 
			
		||||
 | 
			
		||||
        container = LinearLayout(context).apply {
 | 
			
		||||
            orientation = LinearLayout.VERTICAL
 | 
			
		||||
            layoutParams = FrameLayout.LayoutParams(
 | 
			
		||||
                FrameLayout.LayoutParams.MATCH_PARENT,
 | 
			
		||||
                FrameLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
            ).apply {
 | 
			
		||||
                gravity = Gravity.TOP or Gravity.END
 | 
			
		||||
                marginEnd = 20
 | 
			
		||||
                marginStart = 20
 | 
			
		||||
                topMargin = 100
 | 
			
		||||
            }
 | 
			
		||||
            setBackgroundColor(Color.WHITE)
 | 
			
		||||
            setPadding(20, 20, 20, 20)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set up the container layout
 | 
			
		||||
        assembliesProgressBar = ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal).apply {
 | 
			
		||||
            layoutParams = LinearLayout.LayoutParams(
 | 
			
		||||
                LinearLayout.LayoutParams.MATCH_PARENT,
 | 
			
		||||
                LinearLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
            ).apply {
 | 
			
		||||
                topMargin = 20
 | 
			
		||||
            }
 | 
			
		||||
            max = 100
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set up the class progress bar
 | 
			
		||||
        classProgressBar = ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal).apply {
 | 
			
		||||
            layoutParams = LinearLayout.LayoutParams(
 | 
			
		||||
                LinearLayout.LayoutParams.MATCH_PARENT,
 | 
			
		||||
                LinearLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
            ).apply {
 | 
			
		||||
                topMargin = 20
 | 
			
		||||
            }
 | 
			
		||||
            max = 100
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assembliesProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FFF89400"))
 | 
			
		||||
        classProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FFF89400"))
 | 
			
		||||
 | 
			
		||||
        // Set up the text views
 | 
			
		||||
        titleText = TextView(context).apply {
 | 
			
		||||
            layoutParams = FrameLayout.LayoutParams(
 | 
			
		||||
                FrameLayout.LayoutParams.WRAP_CONTENT,
 | 
			
		||||
                FrameLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
            ).apply {
 | 
			
		||||
                topMargin = 20
 | 
			
		||||
                gravity = Gravity.CENTER_HORIZONTAL
 | 
			
		||||
            }
 | 
			
		||||
            setTextColor(Color.BLACK)
 | 
			
		||||
            text = "Initializing"
 | 
			
		||||
            textSize = 20f
 | 
			
		||||
            setTypeface(typeface, Typeface.BOLD)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val textLayout = FrameLayout.LayoutParams(
 | 
			
		||||
            FrameLayout.LayoutParams.WRAP_CONTENT,
 | 
			
		||||
            FrameLayout.LayoutParams.WRAP_CONTENT
 | 
			
		||||
        ).apply {
 | 
			
		||||
            topMargin = 20
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        assembliesProgressText = TextView(context).apply {
 | 
			
		||||
            layoutParams = textLayout
 | 
			
		||||
            setTextColor(Color.BLACK)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        classProgressText = TextView(context).apply {
 | 
			
		||||
            layoutParams = textLayout
 | 
			
		||||
            setTextColor(Color.BLACK)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Add container to the root view
 | 
			
		||||
        context.runOnUiThread {
 | 
			
		||||
            // Add views to the container
 | 
			
		||||
            container.addView(titleText)
 | 
			
		||||
            container.addView(assembliesProgressText)
 | 
			
		||||
            container.addView(assembliesProgressBar)
 | 
			
		||||
            container.addView(classProgressText)
 | 
			
		||||
            container.addView(classProgressBar)
 | 
			
		||||
 | 
			
		||||
            rootView.addView(container)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
    @OptIn(DelicateCoroutinesApi::class)
 | 
			
		||||
    fun finishLoad(context: Activity) {
 | 
			
		||||
        if (!uiCreated) return
 | 
			
		||||
        uiCreated = false
 | 
			
		||||
        GlobalScope.launch {
 | 
			
		||||
            context.runOnUiThread {
 | 
			
		||||
                assembliesProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FF28B463"))
 | 
			
		||||
                classProgressBar.progressTintList = ColorStateList.valueOf(Color.parseColor("#FF28B463"))
 | 
			
		||||
                titleText.text = "Finished"
 | 
			
		||||
            }
 | 
			
		||||
            delay(1500L)
 | 
			
		||||
            context.runOnUiThread {
 | 
			
		||||
                rootView.removeView(container)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun removeView(context: Activity) {
 | 
			
		||||
        if (!uiCreated) return
 | 
			
		||||
        uiCreated = false
 | 
			
		||||
        context.runOnUiThread {
 | 
			
		||||
            rootView.removeView(container)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressLint("SetTextI18n")
 | 
			
		||||
    fun updateData(context: Activity) {
 | 
			
		||||
        if (!uiCreated) return
 | 
			
		||||
        //return
 | 
			
		||||
 | 
			
		||||
        context.runOnUiThread {
 | 
			
		||||
            val assembliesProgress = NativeInitProgress.assembliesProgress
 | 
			
		||||
            val classProgress = NativeInitProgress.classProgress
 | 
			
		||||
 | 
			
		||||
            assembliesProgressText.text = "${assembliesProgress.current}/${assembliesProgress.total}"
 | 
			
		||||
            classProgressText.text = "${classProgress.current}/${classProgress.total}"
 | 
			
		||||
 | 
			
		||||
            assembliesProgressBar.setProgress((assembliesProgress.current * 100 / assembliesProgress.total).toInt(), true)
 | 
			
		||||
            classProgressBar.setProgress((classProgress.current * 100 / classProgress.total).toInt(), true)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -141,9 +141,14 @@ fun HomePage(modifier: Modifier = Modifier,
 | 
			
		|||
                            v -> context?.onEnabledChanged(v)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    GakuSwitch(modifier, stringResource(R.string.lazy_init), checked = config.value.lazyInit) {
 | 
			
		||||
                            v -> context?.onLazyInitChanged(v)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    GakuSwitch(modifier, stringResource(R.string.replace_font), checked = config.value.replaceFont) {
 | 
			
		||||
                            v -> context?.onReplaceFontChanged(v)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Spacer(Modifier.height(6.dp))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
    <string name="gakumas_localify">Gakumas Localify</string>
 | 
			
		||||
    <string name="enable_plugin">启用插件 (不可热重载)</string>
 | 
			
		||||
    <string name="replace_font">替换字体</string>
 | 
			
		||||
    <string name="lazy_init">快速初始化(懒加载配置)</string>
 | 
			
		||||
    <string name="enable_free_camera">启用自由视角(可热重载; 需使用实体键盘)</string>
 | 
			
		||||
    <string name="start_game">以上述配置启动游戏/重载配置</string>
 | 
			
		||||
    <string name="setFpsTitle">最大 FPS (0 为保持游戏原设置)</string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
    <string name="gakumas_localify">Gakumas Localify</string>
 | 
			
		||||
    <string name="enable_plugin">Enable Plugin (Not Hot Reloadable)</string>
 | 
			
		||||
    <string name="replace_font">Replace Font</string>
 | 
			
		||||
    <string name="lazy_init">Fast Initialization (Lazy loading)</string>
 | 
			
		||||
    <string name="enable_free_camera">Enable Free Camera</string>
 | 
			
		||||
    <string name="start_game">Start Game / Hot Reload Config</string>
 | 
			
		||||
    <string name="setFpsTitle">Max FPS (0 is Use Original Settings)</string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue