1
0
Fork 0

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:
chinosk 2024-07-08 20:13:23 +08:00 committed by GitHub
parent c28e05e271
commit 6ddf4212d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 423 additions and 24 deletions

View File

@ -12,21 +12,22 @@ jobs:
with: with:
submodules: recursive 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 - name: Setup Java JDK
uses: actions/setup-java@v4.2.1 uses: actions/setup-java@v4.2.1
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '21' 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 - name: Update Submodules
run: | run: |
git submodule foreach --recursive 'git pull --rebase origin main --allow-unrelated-histories' git submodule foreach --recursive 'git pull --rebase origin main --allow-unrelated-histories'
@ -58,24 +59,44 @@ jobs:
run: ./gradlew build run: ./gradlew build
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
id: upload_unsigned_v4
with: with:
name: GakumasLocalify-Unsigned-apk name: GakumasLocalify-Unsigned-apk
path: app/build/outputs/apk/debug/app-debug.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 name: Sign app APK
id: sign_app id: sign_app
with: with:
releaseDir: app/build/outputs/apk/debug releaseDirectory: app/build/outputs/apk/debug
signingKey: ${{ secrets.KEYSTOREB64 }} signingKeyBase64: ${{ secrets.KEYSTOREB64 }}
keyAlias: ${{ secrets.ANDROID_KEY_ALIAS }} alias: ${{ secrets.ANDROID_KEY_ALIAS }}
keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }} keyStorePassword: ${{ secrets.ANDROID_KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }} keyPassword: ${{ secrets.ANDROID_KEY_PASSWORD }}
buildToolsVersion: 33.0.0 env:
BUILD_TOOLS_VERSION: "34.0.0"
continue-on-error: true continue-on-error: true
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v4
id: upload_signed_v4
with: with:
name: GakumasLocalify-Signed-apk 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 continue-on-error: true

View File

@ -39,7 +39,8 @@ std::unordered_set<void*> hookedStubs{};
GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr); \ 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() { void UnHookAll() {
for (const auto i: hookedStubs) { for (const auto i: hookedStubs) {
@ -97,7 +98,7 @@ namespace GakumasLocal::HookMain {
UnityResolve::UnityType::Transform* cameraTransformCache = nullptr; UnityResolve::UnityType::Transform* cameraTransformCache = nullptr;
void CheckAndUpdateMainCamera() { void CheckAndUpdateMainCamera() {
if (!Config::enableFreeCamera) return; if (!Config::enableFreeCamera) return;
if (IsNativeObjectAlive(mainCameraCache)) return; if (IsNativeObjectAlive(mainCameraCache) && IsNativeObjectAlive(cameraTransformCache)) return;
mainCameraCache = UnityResolve::UnityType::Camera::GetMain(); mainCameraCache = UnityResolve::UnityType::Camera::GetMain();
cameraTransformCache = mainCameraCache->GetTransform(); cameraTransformCache = mainCameraCache->GetTransform();
@ -827,7 +828,8 @@ namespace GakumasLocal::HookMain {
void StartInjectFunctions() { void StartInjectFunctions() {
const auto hookInstaller = Plugin::GetInstance().GetHookInstaller(); 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( ADD_HOOK(AssetBundle_LoadAssetAsync, Il2cppUtils::il2cpp_resolve_icall(
"UnityEngine.AssetBundle::LoadAssetAsync_Internal(System.String,System.Type)")); "UnityEngine.AssetBundle::LoadAssetAsync_Internal(System.String,System.Type)"));
@ -959,10 +961,30 @@ namespace GakumasLocal::HookMain {
Log::Info("Start init plugin..."); 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(); StartInjectFunctions();
GKCamera::initCameraSettings(); GKCamera::initCameraSettings();
if (Config::lazyInit) {
UnityResolveProgress::assembliesProgress.current = 2;
UnityResolveProgress::classProgress.total = 1;
UnityResolveProgress::classProgress.current = 0;
}
Local::LoadData(); Local::LoadData();
if (Config::lazyInit) {
UnityResolveProgress::classProgress.current = 1;
UnityResolveProgress::startInit = false;
}
Log::Info("Plugin init finished."); Log::Info("Plugin init finished.");
return ret; return ret;
} }

View File

@ -7,6 +7,7 @@ namespace GakumasLocal::Config {
bool dbgMode = false; bool dbgMode = false;
bool enabled = true; bool enabled = true;
bool lazyInit = true;
bool replaceFont = true; bool replaceFont = true;
bool forceExportResource = true; bool forceExportResource = true;
bool textTest = false; bool textTest = false;
@ -55,6 +56,7 @@ namespace GakumasLocal::Config {
GetConfigItem(dbgMode); GetConfigItem(dbgMode);
GetConfigItem(enabled); GetConfigItem(enabled);
GetConfigItem(lazyInit);
GetConfigItem(replaceFont); GetConfigItem(replaceFont);
GetConfigItem(forceExportResource); GetConfigItem(forceExportResource);
GetConfigItem(gameOrientation); GetConfigItem(gameOrientation);

View File

@ -5,6 +5,7 @@ namespace GakumasLocal::Config {
extern bool dbgMode; extern bool dbgMode;
extern bool enabled; extern bool enabled;
extern bool lazyInit;
extern bool replaceFont; extern bool replaceFont;
extern bool forceExportResource; extern bool forceExportResource;
extern int gameOrientation; extern int gameOrientation;

View File

@ -47,6 +47,18 @@
#include "../../GakumasLocalify/Log.h" #include "../../GakumasLocalify/Log.h"
#include "../../GakumasLocalify/Misc.hpp" #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 { class UnityResolve final {
public: public:
struct Assembly; struct Assembly;
@ -69,8 +81,16 @@ public:
[[nodiscard]] auto Get(const std::string& strClass, const std::string& strNamespace = "*", const std::string& strParent = "*") const -> Class* { [[nodiscard]] auto Get(const std::string& strClass, const std::string& strNamespace = "*", const std::string& strParent = "*") const -> Class* {
if (!this) return nullptr; 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; 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; mode_ = mode;
hmodule_ = hmodule; hmodule_ = hmodule;
lazyInit_ = lazyInit;
if (mode_ == Mode::Il2Cpp) { if (mode_ == Mode::Il2Cpp) {
if (!lazyInit) UnityResolveProgress::startInit = true;
pDomain = Invoke<void*>("il2cpp_domain_get"); pDomain = Invoke<void*>("il2cpp_domain_get");
Invoke<void*>("il2cpp_thread_attach", pDomain); Invoke<void*>("il2cpp_thread_attach", pDomain);
ForeachAssembly(); ForeachAssembly();
if (!lazyInit) UnityResolveProgress::startInit = false;
} }
else { else {
pDomain = Invoke<void*>("mono_get_root_domain"); pDomain = Invoke<void*>("mono_get_root_domain");
@ -561,7 +584,11 @@ private:
if (mode_ == Mode::Il2Cpp) { if (mode_ == Mode::Il2Cpp) {
size_t nrofassemblies = 0; size_t nrofassemblies = 0;
const auto assemblies = Invoke<void**>("il2cpp_domain_get_assemblies", pDomain, &nrofassemblies); const auto assemblies = Invoke<void**>("il2cpp_domain_get_assemblies", pDomain, &nrofassemblies);
if (!lazyInit_) UnityResolveProgress::assembliesProgress.total = nrofassemblies;
for (auto i = 0; i < nrofassemblies; i++) { for (auto i = 0; i < nrofassemblies; i++) {
if (!lazyInit_) UnityResolveProgress::assembliesProgress.current = i + 1;
const auto ptr = assemblies[i]; const auto ptr = assemblies[i];
if (ptr == nullptr) continue; if (ptr == nullptr) continue;
auto assembly = new Assembly{ .address = ptr }; auto assembly = new Assembly{ .address = ptr };
@ -569,7 +596,9 @@ private:
assembly->file = Invoke<const char*>("il2cpp_image_get_filename", image); assembly->file = Invoke<const char*>("il2cpp_image_get_filename", image);
assembly->name = Invoke<const char*>("il2cpp_image_get_name", image); assembly->name = Invoke<const char*>("il2cpp_image_get_name", image);
UnityResolve::assembly.push_back(assembly); UnityResolve::assembly.push_back(assembly);
ForeachClass(assembly, image); if (!lazyInit_) {
ForeachClass(assembly, image);
}
} }
} }
else { 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 { static auto ForeachClass(Assembly* assembly, void* image) -> void {
// 遍历类 // 遍历类
if (mode_ == Mode::Il2Cpp) { if (mode_ == Mode::Il2Cpp) {
const auto count = Invoke<int>("il2cpp_image_get_class_count", image); const auto count = Invoke<int>("il2cpp_image_get_class_count", image);
if (!lazyInit_) UnityResolveProgress::classProgress.total = count;
for (auto i = 0; i < count; i++) { 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); const auto pClass = Invoke<void*>("il2cpp_image_get_class", image, i);
if (pClass == nullptr) continue; if (pClass == nullptr) continue;
const auto pAClass = new Class(); const auto pAClass = new Class();
@ -2586,6 +2664,7 @@ public:
private: private:
inline static Mode mode_{}; inline static Mode mode_{};
inline static void* hmodule_; inline static void* hmodule_;
inline static bool lazyInit_;
inline static std::unordered_map<std::string, void*> address_{}; inline static std::unordered_map<std::string, void*> address_{};
inline static void* pDomain{}; inline static void* pDomain{};
}; };

View File

@ -15,6 +15,10 @@ JavaVM* g_javaVM = nullptr;
jclass g_gakumasHookMainClass = nullptr; jclass g_gakumasHookMainClass = nullptr;
jmethodID showToastMethodId = nullptr; jmethodID showToastMethodId = nullptr;
bool UnityResolveProgress::startInit = false;
UnityResolveProgress::Progress UnityResolveProgress::assembliesProgress{};
UnityResolveProgress::Progress UnityResolveProgress::classProgress{};
namespace namespace
{ {
class AndroidHookInstaller : public GakumasLocal::HookInstaller class AndroidHookInstaller : public GakumasLocal::HookInstaller
@ -114,8 +118,37 @@ Java_io_github_chinosk_gakumas_localify_GakumasHookMain_loadConfig(JNIEnv *env,
} }
extern "C" extern "C"
JNIEXPORT void JNICALL JNIEXPORT jint JNICALL
Java_io_github_chinosk_gakumas_localify_GakumasHookMain_pluginCallbackLooper(JNIEnv *env, Java_io_github_chinosk_gakumas_localify_GakumasHookMain_pluginCallbackLooper(JNIEnv *env,
jclass clazz) { jclass clazz) {
GakumasLocal::Log::ToastLoop(env, 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);
} }

View File

@ -17,6 +17,7 @@ interface ConfigListener {
fun onForceExportResourceChanged(value: Boolean) fun onForceExportResourceChanged(value: Boolean)
fun onTextTestChanged(value: Boolean) fun onTextTestChanged(value: Boolean)
fun onReplaceFontChanged(value: Boolean) fun onReplaceFontChanged(value: Boolean)
fun onLazyInitChanged(value: Boolean)
fun onEnableFreeCameraChanged(value: Boolean) fun onEnableFreeCameraChanged(value: Boolean)
fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int) fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int)
fun onUnlockAllLiveChanged(value: Boolean) fun onUnlockAllLiveChanged(value: Boolean)
@ -111,6 +112,11 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
pushKeyEvent(KeyEvent(1145, 30)) pushKeyEvent(KeyEvent(1145, 30))
} }
override fun onLazyInitChanged(value: Boolean) {
config.lazyInit = value
saveConfig()
}
override fun onTextTestChanged(value: Boolean) { override fun onTextTestChanged(value: Boolean) {
config.textTest = value config.textTest = value
saveConfig() saveConfig()

View File

@ -34,7 +34,9 @@ import java.util.Locale
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
import io.github.chinosk.gakumas.localify.mainUtils.json 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.models.ProgramConfig
import io.github.chinosk.gakumas.localify.ui.game_attach.InitProgressUI
val TAG = "GakumasLocalify" val TAG = "GakumasLocalify"
@ -49,6 +51,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
private var getConfigError: Exception? = null private var getConfigError: Exception? = null
private var externalFilesChecked: Boolean = false private var externalFilesChecked: Boolean = false
private var gameActivity: Activity? = null
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
// if (lpparam.packageName == "io.github.chinosk.gakumas.localify") { // if (lpparam.packageName == "io.github.chinosk.gakumas.localify") {
@ -135,6 +138,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
super.beforeHookedMethod(param) super.beforeHookedMethod(param)
Log.d(TAG, "onStart") Log.d(TAG, "onStart")
val currActivity = param.thisObject as Activity val currActivity = param.thisObject as Activity
gameActivity = currActivity
if (getConfigError != null) { if (getConfigError != null) {
showGetConfigFailed(currActivity) showGetConfigFailed(currActivity)
} }
@ -148,6 +152,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
override fun beforeHookedMethod(param: MethodHookParam) { override fun beforeHookedMethod(param: MethodHookParam) {
Log.d(TAG, "onResume") Log.d(TAG, "onResume")
val currActivity = param.thisObject as Activity val currActivity = param.thisObject as Activity
gameActivity = currActivity
if (getConfigError != null) { if (getConfigError != null) {
showGetConfigFailed(currActivity) showGetConfigFailed(currActivity)
} }
@ -206,9 +211,30 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
private fun startLoop() { private fun startLoop() {
GlobalScope.launch { GlobalScope.launch {
val interval = 1000L / 30 val interval = 1000L / 30
var lastFrameStartInit = NativeInitProgress.startInit
val initProgressUI = InitProgressUI()
while (isActive) { while (isActive) {
val timeTaken = measureTimeMillis { 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) delay(interval - timeTaken)
} }
@ -413,7 +439,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
} }
@JvmStatic @JvmStatic
external fun pluginCallbackLooper() external fun pluginCallbackLooper(): Int
} }
init { init {

View File

@ -6,6 +6,7 @@ import kotlinx.serialization.Serializable
data class GakumasConfig ( data class GakumasConfig (
var dbgMode: Boolean = false, var dbgMode: Boolean = false,
var enabled: Boolean = true, var enabled: Boolean = true,
var lazyInit: Boolean = true,
var replaceFont: Boolean = true, var replaceFont: Boolean = true,
var textTest: Boolean = false, var textTest: Boolean = false,
var dumpText: Boolean = false, var dumpText: Boolean = false,

View File

@ -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)
}

View File

@ -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)
}
}
}

View File

@ -141,9 +141,14 @@ fun HomePage(modifier: Modifier = Modifier,
v -> context?.onEnabledChanged(v) 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) { GakuSwitch(modifier, stringResource(R.string.replace_font), checked = config.value.replaceFont) {
v -> context?.onReplaceFontChanged(v) v -> context?.onReplaceFontChanged(v)
} }
} }
} }
Spacer(Modifier.height(6.dp)) Spacer(Modifier.height(6.dp))

View File

@ -3,6 +3,7 @@
<string name="gakumas_localify">Gakumas Localify</string> <string name="gakumas_localify">Gakumas Localify</string>
<string name="enable_plugin">启用插件 (不可热重载)</string> <string name="enable_plugin">启用插件 (不可热重载)</string>
<string name="replace_font">替换字体</string> <string name="replace_font">替换字体</string>
<string name="lazy_init">快速初始化(懒加载配置)</string>
<string name="enable_free_camera">启用自由视角(可热重载; 需使用实体键盘)</string> <string name="enable_free_camera">启用自由视角(可热重载; 需使用实体键盘)</string>
<string name="start_game">以上述配置启动游戏/重载配置</string> <string name="start_game">以上述配置启动游戏/重载配置</string>
<string name="setFpsTitle">最大 FPS (0 为保持游戏原设置)</string> <string name="setFpsTitle">最大 FPS (0 为保持游戏原设置)</string>

View File

@ -3,6 +3,7 @@
<string name="gakumas_localify">Gakumas Localify</string> <string name="gakumas_localify">Gakumas Localify</string>
<string name="enable_plugin">Enable Plugin (Not Hot Reloadable)</string> <string name="enable_plugin">Enable Plugin (Not Hot Reloadable)</string>
<string name="replace_font">Replace Font</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="enable_free_camera">Enable Free Camera</string>
<string name="start_game">Start Game / Hot Reload Config</string> <string name="start_game">Start Game / Hot Reload Config</string>
<string name="setFpsTitle">Max FPS (0 is Use Original Settings)</string> <string name="setFpsTitle">Max FPS (0 is Use Original Settings)</string>