diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9c89846..2231a6a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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 diff --git a/app/src/main/cpp/GakumasLocalify/Hook.cpp b/app/src/main/cpp/GakumasLocalify/Hook.cpp index 5694fb6..34852ef 100644 --- a/app/src/main/cpp/GakumasLocalify/Hook.cpp +++ b/app/src/main/cpp/GakumasLocalify/Hook.cpp @@ -39,7 +39,8 @@ std::unordered_set 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; } diff --git a/app/src/main/cpp/GakumasLocalify/config/Config.cpp b/app/src/main/cpp/GakumasLocalify/config/Config.cpp index dae0fcd..2493565 100644 --- a/app/src/main/cpp/GakumasLocalify/config/Config.cpp +++ b/app/src/main/cpp/GakumasLocalify/config/Config.cpp @@ -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); diff --git a/app/src/main/cpp/GakumasLocalify/config/Config.hpp b/app/src/main/cpp/GakumasLocalify/config/Config.hpp index 38aaa64..cba35b4 100644 --- a/app/src/main/cpp/GakumasLocalify/config/Config.hpp +++ b/app/src/main/cpp/GakumasLocalify/config/Config.hpp @@ -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; diff --git a/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp b/app/src/main/cpp/deps/UnityResolve/UnityResolve.hpp index e49b779..c2d2665 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; @@ -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("il2cpp_assembly_get_image", address); + ForeachClass(const_cast(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(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("il2cpp_domain_get"); Invoke("il2cpp_thread_attach", pDomain); ForeachAssembly(); + if (!lazyInit) UnityResolveProgress::startInit = false; } else { pDomain = Invoke("mono_get_root_domain"); @@ -561,7 +584,11 @@ private: if (mode_ == Mode::Il2Cpp) { size_t nrofassemblies = 0; const auto assemblies = Invoke("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("il2cpp_image_get_filename", image); assembly->name = Invoke("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("il2cpp_image_get_class_count", image); + for (auto i = 0; i < count; i++) { + const auto pClass = Invoke("il2cpp_image_get_class", image, i); + const auto className = Invoke("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("il2cpp_assembly_get_image", assembly->address); + void* pClass; + if (strcmp(namespaze, "*") == 0) { + pClass = GetPClassFromUnknownNamespace(image, klassName); + } + else { + pClass = Invoke("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("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); + pAClass->namespaze = namespaze; + 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); + return pAClass; + } + static auto ForeachClass(Assembly* assembly, void* image) -> void { // 遍历类 if (mode_ == Mode::Il2Cpp) { const auto count = Invoke("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("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 address_{}; inline static void* pDomain{}; }; 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/ConfigUpdateListener.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ConfigUpdateListener.kt index b7117b4..1281bfe 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/ConfigUpdateListener.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ConfigUpdateListener.kt @@ -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() 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/GakumasConfig.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt index cff65cf..3f206bc 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/models/GakumasConfig.kt @@ -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, 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..e96d328 --- /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" + 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) + } + } +} diff --git a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt index 8abde41..aa978d9 100644 --- a/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt +++ b/app/src/main/java/io/github/chinosk/gakumas/localify/ui/pages/subPages/HomePage.kt @@ -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)) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index f53086c..7976009 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -3,6 +3,7 @@ Gakumas Localify 启用插件 (不可热重载) 替换字体 + 快速初始化(懒加载配置) 启用自由视角(可热重载; 需使用实体键盘) 以上述配置启动游戏/重载配置 最大 FPS (0 为保持游戏原设置) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 49e079e..c79f892 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -3,6 +3,7 @@ Gakumas Localify Enable Plugin (Not Hot Reloadable) Replace Font + Fast Initialization (Lazy loading) Enable Free Camera Start Game / Hot Reload Config Max FPS (0 is Use Original Settings)