From c810a27ba2d1b68f33b49cf1b1ffd67e9b59b2e3 Mon Sep 17 00:00:00 2001 From: chinosk <2248589280@qq.com> Date: Wed, 3 Jul 2024 20:29:11 +0800 Subject: [PATCH] add plugin init progress --- .../cpp/deps/UnityResolve/UnityResolve.hpp | 20 ++ app/src/main/cpp/libMarryKotone.cpp | 35 +++- .../gakumas/localify/GakumasHookMain.kt | 30 ++- .../localify/models/NativeInitProgress.kt | 25 +++ .../localify/ui/game_attach/InitProgressUI.kt | 176 ++++++++++++++++++ 5 files changed, 283 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/io/github/chinosk/gakumas/localify/models/NativeInitProgress.kt create mode 100644 app/src/main/java/io/github/chinosk/gakumas/localify/ui/game_attach/InitProgressUI.kt diff --git a/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp b/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp index e49b779..4ca2d48 100644 --- a/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp +++ b/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp @@ -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; @@ -284,9 +296,11 @@ public: hmodule_ = hmodule; if (mode_ == Mode::Il2Cpp) { + UnityResolveProgress::startInit = true; pDomain = Invoke("il2cpp_domain_get"); Invoke("il2cpp_thread_attach", pDomain); ForeachAssembly(); + UnityResolveProgress::startInit = false; } else { pDomain = Invoke("mono_get_root_domain"); @@ -561,7 +575,11 @@ private: if (mode_ == Mode::Il2Cpp) { size_t nrofassemblies = 0; const auto assemblies = Invoke("il2cpp_domain_get_assemblies", pDomain, &nrofassemblies); + + UnityResolveProgress::assembliesProgress.total = nrofassemblies; + for (auto i = 0; i < nrofassemblies; i++) { + UnityResolveProgress::assembliesProgress.current = i + 1; const auto ptr = assemblies[i]; if (ptr == nullptr) continue; auto assembly = new Assembly{ .address = ptr }; @@ -594,7 +612,9 @@ private: // 遍历类 if (mode_ == Mode::Il2Cpp) { const auto count = Invoke("il2cpp_image_get_class_count", image); + UnityResolveProgress::classProgress.total = count; for (auto i = 0; i < count; i++) { + UnityResolveProgress::classProgress.current = i + 1; const auto pClass = Invoke("il2cpp_image_get_class", image, i); if (pClass == nullptr) continue; const auto pAClass = new Class(); diff --git a/app/src/main/cpp/libMarryKotone.cpp b/app/src/main/cpp/libMarryKotone.cpp index d63b360..895c949 100644 --- a/app/src/main/cpp/libMarryKotone.cpp +++ b/app/src/main/cpp/libMarryKotone.cpp @@ -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); + } \ No newline at end of file diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/GakumasHookMain.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/GakumasHookMain.kt index 81dee82..0ebf25e 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/GakumasHookMain.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/GakumasHookMain.kt @@ -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 { diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/models/NativeInitProgress.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/models/NativeInitProgress.kt new file mode 100644 index 0000000..3915e12 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/models/NativeInitProgress.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/game_attach/InitProgressUI.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/game_attach/InitProgressUI.kt new file mode 100644 index 0000000..8ba00d4 --- /dev/null +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/game_attach/InitProgressUI.kt @@ -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(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 Plugins" + 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) + } + } +}