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:
|
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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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{};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
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))
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue