Compare commits

...

15 Commits

Author SHA1 Message Date
WeiguangTWK cabb902a41 Strings: Correct strings that mistakenly replaced 2026-04-02 22:42:31 +08:00
WeiguangTWK 04b467992f Strings: Update info about GKMSPatch 2026-04-02 19:59:54 +08:00
WeiguangTWK ca3fb1b05a native: Drop C++17 deprecated codecvt and use JNI instead, add modern ICU lib for future android API 31 2026-03-31 15:38:43 +08:00
WeiguangTWK cbc571cf7a UnityResolve: Solve C++ 17 return warnings 2026-03-31 15:16:36 +08:00
WeiguangTWK 1256b12d9a treewide: Declare static scope 2026-03-31 15:15:58 +08:00
WeiguangTWK 6fc1873b27 GakumasHookMain: Add notes back 2026-03-30 17:34:40 +08:00
WeiguangTWK 3df995e3bc README: Update info 2026-03-30 17:23:52 +08:00
WeiguangTWK a12651d60f MainActivity: Add dialogue to inform that Patcher is now unavailable 2026-03-30 17:16:40 +08:00
WeiguangTWK 048827feee Treewide: Migrate XPosed API to 101 2026-03-30 17:14:52 +08:00
chinosk c7af3e41a5
bump version 2026-03-19 17:36:35 +08:00
chinosk 87c4f366df Merge pull request '只替换普通字体、保留特殊数字' (#2) from chihya72/gkms-local:main into main
Reviewed-on: chinosk/gkms-local#2
2026-03-19 16:31:29 +08:00
pm chihya a39b024b58 只替换普通字体、保留特殊数字
添加新genericTrans hook点
2026-03-13 16:48:43 +08:00
chinosk 3110cc7e49
Filtered unnecessary space. Updated LSPatch version. 2025-12-10 13:15:12 +08:00
chinosk b70376dcef
bump version to `3.1.0` 2025-06-06 03:11:20 +01:00
chinosk e859589125
fixed live 2025-04-27 15:50:38 +01:00
31 changed files with 898 additions and 324 deletions

View File

@ -2,6 +2,8 @@
- 学园偶像大师 本地化插件 - 学园偶像大师 本地化插件
- **开发中** - **开发中**
- 下游更改将API版本提升至101并暂时禁用Patcher

View File

@ -16,7 +16,7 @@ android {
minSdk 29 minSdk 29
targetSdk 34 targetSdk 34
versionCode 12 versionCode 12
versionName "v3.0.0" versionName "v3.2.0"
buildConfigField "String", "VERSION_NAME", "\"${versionName}\"" buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -130,6 +130,6 @@ dependencies {
implementation(libs.xdl) implementation(libs.xdl)
implementation(libs.shadowhook) implementation(libs.shadowhook)
compileOnly(libs.xposed.api) compileOnly(libs.libxposed.api)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
} }

Binary file not shown.

Binary file not shown.

View File

@ -2,33 +2,33 @@ import os
logs = """ logs = """
Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules listenablefuture-1.0.jar -> listenablefuture-1.0 (com.google.guava:listenablefuture:1.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules listenablefuture-1.0.jar -> listenablefuture-1.0 (com.google.guava:listenablefuture:1.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.CanIgnoreReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.CanIgnoreReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.CheckReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.CheckReturnValue found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.CompatibleWith found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.CompatibleWith found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.CompileTimeConstant found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.CompileTimeConstant found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.DoNotCall found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.DoNotCall found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.DoNotMock found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.DoNotMock found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.ForOverride found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.ForOverride found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.FormatMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.FormatMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.FormatString found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.FormatString found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.Immutable found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.Immutable found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.IncompatibleModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.IncompatibleModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.InlineMe found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.InlineMe found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.InlineMeValidationDisabled found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.InlineMeValidationDisabled found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.Keep found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.Keep found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.Modifier found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.Modifier found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.MustBeClosed found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.MustBeClosed found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.NoAllocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.NoAllocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.OverridingMethodsMustInvokeSuper found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.RequiredModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.RequiredModifiers found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.RestrictedApi found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.RestrictedApi found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.SuppressPackageLocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.SuppressPackageLocation found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.Var found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.Var found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.concurrent.GuardedBy found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.concurrent.GuardedBy found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.concurrent.LazyInit found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.concurrent.LazyInit found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.concurrent.LockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.concurrent.LockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
Duplicate class com.google.errorprone.annotations.concurrent.UnlockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch.jar -> lspatch (lspatch.jar) Duplicate class com.google.errorprone.annotations.concurrent.UnlockMethod found in modules error_prone_annotations-2.15.0.jar -> error_prone_annotations-2.15.0 (com.google.errorprone:error_prone_annotations:2.15.0) and lspatch_cleaned.jar -> lspatch_cleaned (lspatch_cleaned.jar)
""" """
for i in logs.split("\n"): for i in logs.split("\n"):

View File

@ -21,23 +21,6 @@
android:usesCleartextTraffic="true" android:usesCleartextTraffic="true"
tools:targetApi="31"> tools:targetApi="31">
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="IDOLM@STER Gakuen localify" />
<meta-data
android:name="xposedminversion"
android:value="53" />
<meta-data
android:name="xposedsharedprefs"
android:value="true" />
<meta-data
android:name="xposedscope"
android:value="com.bandainamcoent.idolmaster_gakuen" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
@ -101,4 +84,4 @@
</application> </application>
</manifest> </manifest>

View File

@ -1 +0,0 @@
io.github.chinosk.gakumas.localify.GakumasHookMain

View File

@ -68,4 +68,8 @@ target_link_libraries(${CMAKE_PROJECT_NAME}
log log
fmt) fmt)
if (ANDROID AND ANDROID_PLATFORM_LEVEL GREATER_EQUAL 31)
target_link_libraries(${CMAKE_PROJECT_NAME} icu)
endif()
target_compile_features(${CMAKE_PROJECT_NAME} PRIVATE cxx_std_23) target_compile_features(${CMAKE_PROJECT_NAME} PRIVATE cxx_std_23)

View File

@ -6,6 +6,7 @@
#include "Local.h" #include "Local.h"
#include "MasterLocal.h" #include "MasterLocal.h"
#include <unordered_set> #include <unordered_set>
#include <algorithm>
#include "camera/camera.hpp" #include "camera/camera.hpp"
#include "config/Config.hpp" #include "config/Config.hpp"
// #include <jni.h> // #include <jni.h>
@ -99,6 +100,18 @@ namespace GakumasLocal::HookMain {
return GetResolution->Invoke<Il2cppUtils::Resolution_t>(); return GetResolution->Invoke<Il2cppUtils::Resolution_t>();
} }
Il2cppString* ToJsonStr(void* object) {
static Il2cppString* (*toJsonStr)(void*) = nullptr;
if (!toJsonStr) {
toJsonStr = reinterpret_cast<Il2cppString * (*)(void*)>(Il2cppUtils::GetMethodPointer("Newtonsoft.Json.dll", "Newtonsoft.Json",
"JsonConvert", "SerializeObject", { "*" }));
}
if (!toJsonStr) {
return nullptr;
}
return toJsonStr(object);
}
DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* self, float value)) { DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* self, float value)) {
if (Config::enableFreeCamera) { if (Config::enableFreeCamera) {
if (self == mainCameraCache) { if (self == mainCameraCache) {
@ -454,6 +467,8 @@ namespace GakumasLocal::HookMain {
"TMPro", "TMP_Text", "get_font"); "TMPro", "TMP_Text", "get_font");
static auto set_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", static auto set_font = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
"TMPro", "TMP_Text", "set_font"); "TMPro", "TMP_Text", "set_font");
static auto get_name = Il2cppUtils::GetMethod("UnityEngine.CoreModule.dll",
"UnityEngine", "Object", "get_name");
// static auto set_fontMaterial = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", // static auto set_fontMaterial = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
// "TMPro", "TMP_Text", "set_fontMaterial"); // "TMPro", "TMP_Text", "set_fontMaterial");
// static auto ForceMeshUpdate = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", // static auto ForceMeshUpdate = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll",
@ -467,21 +482,32 @@ namespace GakumasLocal::HookMain {
static auto UpdateFontAssetData = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro", static auto UpdateFontAssetData = Il2cppUtils::GetMethod("Unity.TextMeshPro.dll", "TMPro",
"TMP_FontAsset", "UpdateFontAssetData"); "TMP_FontAsset", "UpdateFontAssetData");
auto fontAsset = get_font->Invoke<void*>(TMP_Textself);
if (!fontAsset) {
return;
}
// 检查字体名称,跳过 CampusAlphanumeric 系列字体
auto fontAssetName = get_name->Invoke<Il2cppString*>(fontAsset);
if (fontAssetName) {
std::string fontName = fontAssetName->ToString();
std::transform(fontName.begin(), fontName.end(), fontName.begin(),
[](unsigned char c) { return std::tolower(c); });
if (fontName.find("campusalphanumeric") != std::string::npos) {
return; // 保持原版数字字体
}
}
auto newFont = GetReplaceFont(); auto newFont = GetReplaceFont();
if (!newFont) return; if (!newFont) return;
auto fontAsset = get_font->Invoke<void*>(TMP_Textself); set_sourceFontFile->Invoke<void>(fontAsset, newFont);
if (fontAsset) { if (!updatedFontPtrs.contains(fontAsset)) {
set_sourceFontFile->Invoke<void>(fontAsset, newFont); updatedFontPtrs.emplace(fontAsset);
if (!updatedFontPtrs.contains(fontAsset)) { UpdateFontAssetData->Invoke<void>(fontAsset);
updatedFontPtrs.emplace(fontAsset);
UpdateFontAssetData->Invoke<void>(fontAsset);
}
if (updatedFontPtrs.size() > 200) updatedFontPtrs.clear();
}
else {
Log::Error("UpdateFont: fontAsset is null.");
} }
if (updatedFontPtrs.size() > 200) updatedFontPtrs.clear();
set_font->Invoke<void>(TMP_Textself, fontAsset); set_font->Invoke<void>(TMP_Textself, fontAsset);
// auto fontMaterial = get_material->Invoke<void*>(fontAsset); // auto fontMaterial = get_material->Invoke<void*>(fontAsset);
@ -514,6 +540,66 @@ namespace GakumasLocal::HookMain {
UpdateFont(self); UpdateFont(self);
} }
DEFINE_HOOK(void, TMP_Text_set_text, (void* self, Il2cppString* value, void* mtd)) {
if (!value) {
return TMP_Text_set_text_Orig(self, value, mtd);
}
const std::string origText = value->ToString();
std::string transText;
if (Local::GetGenericText(origText, &transText)) {
const auto newText = UnityResolve::UnityType::String::New(transText);
UpdateFont(self);
return TMP_Text_set_text_Orig(self, newText, mtd);
}
if (Config::textTest) {
TMP_Text_set_text_Orig(self, UnityResolve::UnityType::String::New("[TT]" + origText), mtd);
}
else {
TMP_Text_set_text_Orig(self, value, mtd);
}
UpdateFont(self);
}
DEFINE_HOOK(void, TMP_Text_SetText_1, (void* self, Il2cppString* sourceText, void* mtd)) {
if (!sourceText) {
return TMP_Text_SetText_1_Orig(self, sourceText, mtd);
}
const std::string origText = sourceText->ToString();
std::string transText;
if (Local::GetGenericText(origText, &transText)) {
const auto newText = UnityResolve::UnityType::String::New(transText);
UpdateFont(self);
return TMP_Text_SetText_1_Orig(self, newText, mtd);
}
if (Config::textTest) {
TMP_Text_SetText_1_Orig(self, UnityResolve::UnityType::String::New("[T1]" + origText), mtd);
}
else {
TMP_Text_SetText_1_Orig(self, sourceText, mtd);
}
UpdateFont(self);
}
DEFINE_HOOK(void, TMP_Text_SetText_2, (void* self, Il2cppString* sourceText, bool syncTextInputBox, void* mtd)) {
if (!sourceText) {
return TMP_Text_SetText_2_Orig(self, sourceText, syncTextInputBox, mtd);
}
const std::string origText = sourceText->ToString();
std::string transText;
if (Local::GetGenericText(origText, &transText)) {
const auto newText = UnityResolve::UnityType::String::New(transText);
UpdateFont(self);
return TMP_Text_SetText_2_Orig(self, newText, syncTextInputBox, mtd);
}
if (Config::textTest) {
TMP_Text_SetText_2_Orig(self, UnityResolve::UnityType::String::New("[TS]" + sourceText->ToString()), syncTextInputBox, mtd);
}
else {
TMP_Text_SetText_2_Orig(self, sourceText, syncTextInputBox, mtd);
}
UpdateFont(self);
}
DEFINE_HOOK(void, TextMeshProUGUI_Awake, (void* self, void* method)) { DEFINE_HOOK(void, TextMeshProUGUI_Awake, (void* self, void* method)) {
// Log::InfoFmt("TextMeshProUGUI_Awake at %p, self at %p", TextMeshProUGUI_Awake_Orig, self); // Log::InfoFmt("TextMeshProUGUI_Awake at %p, self at %p", TextMeshProUGUI_Awake_Orig, self);
@ -540,9 +626,64 @@ namespace GakumasLocal::HookMain {
TextMeshProUGUI_Awake_Orig(self, method); TextMeshProUGUI_Awake_Orig(self, method);
} }
// TODO 文本未hook完整 // Legacy UnityEngine.UI.Text hook礼物/邮件等非TMP界面
DEFINE_HOOK(void, UIText_set_text, (void* self, Il2cppString* value)) {
if (!value) {
return UIText_set_text_Orig(self, value);
}
const std::string origText = value->ToString();
std::string transText;
if (Local::GetGenericText(origText, &transText)) {
const auto newText = UnityResolve::UnityType::String::New(transText);
return UIText_set_text_Orig(self, newText);
}
if (Config::textTest) {
UIText_set_text_Orig(self, UnityResolve::UnityType::String::New("[UI]" + origText));
}
else {
UIText_set_text_Orig(self, value);
}
}
// TMP_Text.SetCharArray(char[], int, int) — 礼物/邮件描述文字通过此路径设置
DEFINE_HOOK(void, TMP_Text_SetCharArray, (void* self, void* charArray, int start, int count, void* mtd)) {
if (charArray && start >= 0 && count > 0) {
// IL2CPP char[] elements are uint16_t (UTF-16)
auto arr = reinterpret_cast<UnityResolve::UnityType::Array<uint16_t>*>(charArray);
// 边界检查:确保 start+count 不超出数组长度
if (static_cast<uintptr_t>(start + count) <= arr->max_length) {
auto rawData = arr->GetData();
if (rawData) {
// rawData 是 uintptr_t字节地址每个 char16_t 占 2 字节
// 必须用 start * sizeof(char16_t) 而非直接 + start否则偏移量减半
const std::u16string u16(
reinterpret_cast<const char16_t*>(rawData + static_cast<uintptr_t>(start) * sizeof(char16_t)),
static_cast<size_t>(count));
const std::string origText = Misc::ToUTF8(u16);
std::string transText;
if (Local::GetGenericText(origText, &transText)) {
UpdateFont(self);
TMP_Text_set_text_Orig(self, Il2cppString::New(transText), nullptr);
return;
}
if (Config::textTest) {
UpdateFont(self);
TMP_Text_set_text_Orig(self, Il2cppString::New("[CA]" + origText), nullptr);
return;
}
}
}
}
TMP_Text_SetCharArray_Orig(self, charArray, start, count, mtd);
}
DEFINE_HOOK(void, TextField_set_value, (void* self, Il2cppString* value)) { DEFINE_HOOK(void, TextField_set_value, (void* self, Il2cppString* value)) {
Log::DebugFmt("TextField_set_value: %s", value->ToString().c_str()); if (value) {
std::string transText;
if (Local::GetGenericText(value->ToString(), &transText)) {
return TextField_set_value_Orig(self, UnityResolve::UnityType::String::New(transText));
}
}
TextField_set_value_Orig(self, value); TextField_set_value_Orig(self, value);
} }
@ -683,24 +824,24 @@ namespace GakumasLocal::HookMain {
} }
#ifdef GKMS_WINDOWS #ifdef GKMS_WINDOWS
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetDataAsync, (void* retstr, void* self, void* liveData, bool isReleased, bool isUnlocked, bool isNew, bool hasLiveSkin, void* ct, void* mtd)) { DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetDataAsync, (void* retstr, void* self, void* liveData, Il2cppString* characterId, bool isReleased, bool isUnlocked, bool isNew, bool hasLiveSkin, void* ct, void* mtd)) {
// Log::DebugFmt("PictureBookLiveThumbnailView_SetDataAsync: isReleased: %d, isUnlocked: %d, isNew: %d, hasLiveSkin: %d", isReleased, isUnlocked, isNew, hasLiveSkin); // Log::DebugFmt("PictureBookLiveThumbnailView_SetDataAsync: isReleased: %d, isUnlocked: %d, isNew: %d, hasLiveSkin: %d", isReleased, isUnlocked, isNew, hasLiveSkin);
if (Config::dbgMode && Config::unlockAllLive) { if (Config::dbgMode && Config::unlockAllLive) {
isUnlocked = true; isUnlocked = true;
isReleased = true; isReleased = true;
hasLiveSkin = true; hasLiveSkin = true;
} }
PictureBookLiveThumbnailView_SetDataAsync_Orig(retstr, self, liveData, isReleased, isUnlocked, isNew, hasLiveSkin, ct, mtd); PictureBookLiveThumbnailView_SetDataAsync_Orig(retstr, self, liveData, characterId, isReleased, isUnlocked, isNew, hasLiveSkin, ct, mtd);
} }
#else #else
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetDataAsync, (void* self, void* liveData, bool isReleased, bool isUnlocked, bool isNew, bool hasLiveSkin, void* ct, void* mtd)) { DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetDataAsync, (void* self, void* liveData, Il2cppString* characterId, bool isReleased, bool isUnlocked, bool isNew, bool hasLiveSkin, void* ct, void* mtd)) {
// Log::DebugFmt("PictureBookLiveThumbnailView_SetDataAsync: isReleased: %d, isUnlocked: %d, isNew: %d, hasLiveSkin: %d", isReleased, isUnlocked, isNew, hasLiveSkin); // Log::DebugFmt("PictureBookLiveThumbnailView_SetDataAsync: isReleased: %d, isUnlocked: %d, isNew: %d, hasLiveSkin: %d", isReleased, isUnlocked, isNew, hasLiveSkin);
if (Config::dbgMode && Config::unlockAllLive) { if (Config::dbgMode && Config::unlockAllLive) {
isUnlocked = true; isUnlocked = true;
isReleased = true; isReleased = true;
hasLiveSkin = true; hasLiveSkin = true;
} }
PictureBookLiveThumbnailView_SetDataAsync_Orig(self, liveData, isReleased, isUnlocked, isNew, hasLiveSkin, ct, mtd); PictureBookLiveThumbnailView_SetDataAsync_Orig(self, liveData, characterId, isReleased, isUnlocked, isNew, hasLiveSkin, ct, mtd);
} }
#endif #endif
@ -845,6 +986,34 @@ namespace GakumasLocal::HookMain {
return ret; return ret;
} }
void* getCompletedUniTask() {
static auto unitask_klass = Il2cppUtils::GetClass("UniTask.dll", "Cysharp.Threading.Tasks", "UniTask");
static auto CompletedTask_field = unitask_klass->Get<UnityResolve::Field>("CompletedTask");
auto ret = UnityResolve::Invoke<void*>("il2cpp_object_new", unitask_klass->address);
UnityResolve::Invoke<void>("il2cpp_field_static_get_value", CompletedTask_field->address, ret);
return ret;
}
#ifdef GKMS_WINDOWS
// 绕过切歌时的等待以及网络请求
DEFINE_HOOK(void*, Produce_ViewPictureBookLiveAsync, (void* retstr, Il2cppString* musicId, Il2cppString* characterId,
void* ct, void* callOption, void* errorHandlerIl, Il2cppString* requestIdForResponseCache, void* mtd)) {
// Log::DebugFmt("Produce_ViewPictureBookLiveAsync: %s - %s", musicId->ToString().c_str(), characterId->ToString().c_str());
if (Config::unlockAllLive) return getCompletedUniTask();
return Produce_ViewPictureBookLiveAsync_Orig(retstr, musicId, characterId, ct, callOption, errorHandlerIl, requestIdForResponseCache, mtd);
}
#else
DEFINE_HOOK(void*, Produce_ViewPictureBookLiveAsync, (void* retstr, void* musicId, void* characterId,
void* ct, void* callOption, void* errorHandlerIl, void* requestIdForResponseCache, void* mtd, void* wenhao)) {
// Log::DebugFmt("Produce_ViewPictureBookLiveAsync: %s - %s", musicId->ToString().c_str(), characterId->ToString().c_str());
if (Config::unlockAllLive) return getCompletedUniTask();
return Produce_ViewPictureBookLiveAsync_Orig(retstr, musicId, characterId, ct, callOption, errorHandlerIl, requestIdForResponseCache, mtd, wenhao);
}
#endif // GKMS_WINDOWS
void* PictureBookWindowPresenter_instance = nullptr; void* PictureBookWindowPresenter_instance = nullptr;
std::string PictureBookWindowPresenter_charaId; std::string PictureBookWindowPresenter_charaId;
DEFINE_HOOK(void*, PictureBookWindowPresenter_GetLiveMusics, (void* self, Il2cppString* charaId, void* mtd)) { DEFINE_HOOK(void*, PictureBookWindowPresenter_GetLiveMusics, (void* self, Il2cppString* charaId, void* mtd)) {
@ -857,27 +1026,59 @@ namespace GakumasLocal::HookMain {
static auto PictureBookWindowPresenter_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.OutGame", static auto PictureBookWindowPresenter_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookWindowPresenter"); "PictureBookWindowPresenter");
static auto existsMusicIds_field = PictureBookWindowPresenter_klass->Get<UnityResolve::Field>("_existsMusicIds"); static auto existsMusicIds_field = PictureBookWindowPresenter_klass->Get<UnityResolve::Field>("_existsMusicIds");
auto existsMusicIds = Il2cppUtils::ClassGetFieldValue<UnityResolve::UnityType::List<Il2cppString*>*>(self, existsMusicIds_field); // auto existsMusicIds = Il2cppUtils::ClassGetFieldValue<UnityResolve::UnityType::List<Il2cppString*>*>(self, existsMusicIds_field);
auto existsMusicIds = Il2cppUtils::ClassGetFieldValue<UnityResolve::UnityType::Dictionary<Il2cppString*, UnityResolve::UnityType::List<Il2cppString*>>*>(self, existsMusicIds_field);
if (!existsMusicIds) { if (!existsMusicIds) {
static auto Dict_List_String_klass = Il2cppUtils::get_system_class_from_reflection_type_str(
"System.Collections.Generic.Dictionary`2[System.String, System.Collections.Generic.List`1[System.String]]");
static auto List_String_klass = Il2cppUtils::get_system_class_from_reflection_type_str( static auto List_String_klass = Il2cppUtils::get_system_class_from_reflection_type_str(
"System.Collections.Generic.List`1[System.String]"); "System.Collections.Generic.List`1[System.String]");
static auto List_String_ctor_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(List_String_klass, ".ctor", 0); static auto List_String_ctor_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(List_String_klass, ".ctor", 0);
static auto List_String_ctor = reinterpret_cast<void (*)(void*, void*)>(List_String_ctor_mtd->methodPointer); static auto List_String_ctor = reinterpret_cast<void (*)(void*, void*)>(List_String_ctor_mtd->methodPointer);
auto newList = UnityResolve::Invoke<void*>("il2cpp_object_new", List_String_klass);
List_String_ctor(newList, List_String_ctor_mtd);
Il2cppUtils::Tools::CSListEditor<Il2cppString*> newListEditor(newList);
auto fullIds = GetIdolMusicIdAll(); auto fullIds = GetIdolMusicIdAll();
static auto Dict_List_String_ctor_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(Dict_List_String_klass, ".ctor", 0);
static auto Dict_List_String_ctor = reinterpret_cast<void (*)(void*, void*)>(Dict_List_String_ctor_mtd->methodPointer);
auto newDict = UnityResolve::Invoke<void*>("il2cpp_object_new", Dict_List_String_klass);
Dict_List_String_ctor(newDict, Dict_List_String_ctor_mtd);
Il2cppUtils::Tools::CSDictEditor<Il2cppString*, void*> newDictEditor(newDict, Dict_List_String_klass);
// auto fullIds = GetIdolMusicIdAll();
for (auto& i : fullIds) { for (auto& i : fullIds) {
// Log::DebugFmt("GetLiveMusics - Add: %s", i.c_str()); // Log::DebugFmt("GetLiveMusics - Add: %s", i.c_str()); // eg. music-all-amao-001, music-char-hski-001
newListEditor.Add(Il2cppString::New(i)); //newListEditor.Add(Il2cppString::New(i));
auto newList = UnityResolve::Invoke<void*>("il2cpp_object_new", List_String_klass);
List_String_ctor(newList, List_String_ctor_mtd);
newDictEditor.Add(Il2cppString::New(i), newList);
} }
Il2cppUtils::ClassSetFieldValue(self, existsMusicIds_field, newList); Il2cppUtils::ClassSetFieldValue(self, existsMusicIds_field, newDict);
existsMusicIds = reinterpret_cast<decltype(existsMusicIds)>(newDict);
// Log::DebugFmt("GetLiveMusics - set end: %d", fullIds.size()); // Log::DebugFmt("GetLiveMusics - set end: %d", fullIds.size());
} }
/*
Il2cppUtils::Tools::CSDictEditor<Il2cppString*, void*> dicCheckEditor(existsMusicIds, Dict_List_String_klass);
for (auto& i : fullIds) {
auto currKeyStr = Il2cppString::New(i);
void* currList;
if (dicCheckEditor.ContainsKey(currKeyStr)) {
currList = dicCheckEditor.get_Item(currKeyStr);
}
else {
currList = UnityResolve::Invoke<void*>("il2cpp_object_new", List_String_klass);
List_String_ctor(currList, List_String_ctor_mtd);
}
Il2cppUtils::Tools::CSListEditor<Il2cppString*> currListEditor(currList);
if (!currListEditor.Contains(charaId)) {
currListEditor.Add(charaId);
}
}*/
} }
return PictureBookWindowPresenter_GetLiveMusics_Orig(self, charaId, mtd); return PictureBookWindowPresenter_GetLiveMusics_Orig(self, charaId, mtd);
@ -886,7 +1087,7 @@ namespace GakumasLocal::HookMain {
DEFINE_HOOK(void, PictureBookLiveSelectScreenModel_ctor, (void* self, void* transitionParam, UnityResolve::UnityType::List<void*>* musics, void* mtd)) { DEFINE_HOOK(void, PictureBookLiveSelectScreenModel_ctor, (void* self, void* transitionParam, UnityResolve::UnityType::List<void*>* musics, void* mtd)) {
// Log::DebugFmt("PictureBookLiveSelectScreenModel_ctor"); // Log::DebugFmt("PictureBookLiveSelectScreenModel_ctor");
if (Config::unlockAllLive) { if (Config::dbgMode && Config::unlockAllLive) {
static auto GetLiveMusics = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame", static auto GetLiveMusics = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookWindowPresenter", "GetLiveMusics"); "PictureBookWindowPresenter", "GetLiveMusics");
if (PictureBookWindowPresenter_instance && !PictureBookWindowPresenter_charaId.empty()) { if (PictureBookWindowPresenter_instance && !PictureBookWindowPresenter_charaId.empty()) {
@ -900,17 +1101,16 @@ namespace GakumasLocal::HookMain {
} }
bool needRestoreHides = false; bool needRestoreHides = false;
DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* self, void* produceLive, DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* self, void* produceLive, bool isPlayCharacterFocusCamera, void* mtd)) {
Il2cppString* characterId, Il2cppString* idolCardId, Il2cppString* costumeId, Il2cppString* costumeHeadId, void* mtd)) {
needRestoreHides = false; needRestoreHides = false;
Log::InfoFmt("MoveLiveScene: characterId: %s, idolCardId: %s, costumeId: %s, costumeHeadId: %s,", // Log::InfoFmt("MoveLiveScene: characterId: %s, idolCardId: %s, costumeId: %s, costumeHeadId: %s,",
characterId->ToString().c_str(), idolCardId->ToString().c_str(), costumeId->ToString().c_str(), costumeHeadId->ToString().c_str()); // characterId->ToString().c_str(), idolCardId->ToString().c_str(), costumeId->ToString().c_str(), costumeHeadId->ToString().c_str());
/* /*
characterId: hski, costumeId: hski-cstm-0002, costumeHeadId: costume_head_hski-cstm-0002, characterId: hski, costumeId: hski-cstm-0002, costumeHeadId: costume_head_hski-cstm-0002,
characterId: shro, costumeId: shro-cstm-0006, costumeHeadId: costume_head_shro-cstm-0006, characterId: shro, costumeId: shro-cstm-0006, costumeHeadId: costume_head_shro-cstm-0006,
*/ */
/*
if (Config::dbgMode && Config::enableLiveCustomeDress) { if (Config::dbgMode && Config::enableLiveCustomeDress) {
// 修改 LiveFixedData_GetCharacter 可以更改 Loading 角色和演唱者名字,而不变更实际登台人 // 修改 LiveFixedData_GetCharacter 可以更改 Loading 角色和演唱者名字,而不变更实际登台人
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, idolCardId, return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, idolCardId,
@ -918,8 +1118,9 @@ namespace GakumasLocal::HookMain {
Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId), Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId),
mtd); mtd);
} }
*/
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, idolCardId, costumeId, costumeHeadId, mtd); // return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, characterId, idolCardId, costumeId, costumeHeadId, mtd);
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(self, produceLive, isPlayCharacterFocusCamera, mtd);
} }
// std::string lastMusicId; // std::string lastMusicId;
@ -1240,6 +1441,70 @@ namespace GakumasLocal::HookMain {
return ret; return ret;
} }
#ifdef GKMS_WINDOWS
// DMM Only
DEFINE_HOOK(void*, WindowHandle_SetWindowLong, (int32_t nIndex, intptr_t dwNewLong, void* mtd)) {
if (GakumasLocal::Config::dmmUnlockSize) {
// Log::DebugFmt("WindowHandle_SetWindowLong: %d, %p\n", nIndex, dwNewLong);
if (nIndex == GWLP_WNDPROC) {
return 0;
}
}
return WindowHandle_SetWindowLong_Orig(nIndex, dwNewLong, mtd);
}
// DMM Only
void SetResolution(int width, int height, bool fullscreen) {
static auto Screen_SetResolution = reinterpret_cast<void (*)(UINT, UINT, UINT, void*)>(
Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Screen::SetResolution_Injected(System.Int32,System.Int32,UnityEngine.FullScreenMode,UnityEngine.RefreshRate&)"));
int64_t v8[3];
v8[0] = 0x100000000LL;
Screen_SetResolution(width, height, 2 * !fullscreen + 1, v8);
}
// DMM Only
DEFINE_HOOK(void, WindowManager_ApplyOrientationSettings, (int orientation, void* method)) {
if (!GakumasLocal::Config::dmmUnlockSize) return WindowManager_ApplyOrientationSettings_Orig(orientation, method);
static auto get_Height = reinterpret_cast<int (*)()>(Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Screen::get_height()"));
static auto get_Width = reinterpret_cast<int (*)()>(Il2cppUtils::il2cpp_resolve_icall("UnityEngine.Screen::get_width()"));
static auto lastWidth = -1;
static auto lastHeight = -1;
const auto currWidth = get_Width();
const auto currHeight = get_Height();
if (lastWidth == -1) {
lastWidth = currWidth;
lastHeight = currHeight;
return;
}
const bool lastIsPortrait = lastWidth < lastHeight;
const bool currIsPortrait = currWidth < currHeight;
if (lastIsPortrait == currIsPortrait) {
lastWidth = currWidth;
lastHeight = currHeight;
return;
}
SetResolution(lastWidth, lastHeight, false);
lastWidth = currWidth;
lastHeight = currHeight;
Log::DebugFmt("WindowManager_ApplyOrientationSettings: %d (%d, %d)\n", orientation, get_Width(), get_Height());
}
// DMM Only
DEFINE_HOOK(void, AspectRatioHandler_NudgeWindow, (void* method)) {
if (!GakumasLocal::Config::dmmUnlockSize) return AspectRatioHandler_NudgeWindow_Orig(method);
// printf("AspectRatioHandler_NudgeWindow\n");
}
#endif
void UpdateSwingBreastBonesData(void* initializeData) { void UpdateSwingBreastBonesData(void* initializeData) {
if (!Config::enableBreastParam) return; if (!Config::enableBreastParam) return;
@ -1441,12 +1706,35 @@ namespace GakumasLocal::HookMain {
ADD_HOOK(TextMeshProUGUI_Awake, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro", ADD_HOOK(TextMeshProUGUI_Awake, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
"TextMeshProUGUI", "Awake")); "TextMeshProUGUI", "Awake"));
ADD_HOOK(TMP_Text_set_text, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
"TMP_Text", "set_text"));
ADD_HOOK(TMP_Text_SetText_1, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
"TMP_Text", "SetText",
{"System.String"}));
ADD_HOOK(TMP_Text_PopulateTextBackingArray, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro", ADD_HOOK(TMP_Text_PopulateTextBackingArray, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
"TMP_Text", "PopulateTextBackingArray", "TMP_Text", "PopulateTextBackingArray",
{"System.String", "System.Int32", "System.Int32"})); {"System.String", "System.Int32", "System.Int32"}));
ADD_HOOK(TMP_Text_SetText_2, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
"TMP_Text", "SetText",
{ "System.String", "System.Boolean" }));
ADD_HOOK(TextField_set_value, Il2cppUtils::GetMethodPointer("UnityEngine.UIElementsModule.dll", "UnityEngine.UIElements", ADD_HOOK(TextField_set_value, Il2cppUtils::GetMethodPointer("UnityEngine.UIElementsModule.dll", "UnityEngine.UIElements",
"TextField", "set_value")); "TextField", "set_value"));
// Legacy UnityEngine.UI.Text hook
{
auto uiTextPtr = Il2cppUtils::GetMethodPointer("UnityEngine.UI.dll", "UnityEngine.UI",
"Text", "set_text");
if (uiTextPtr) {
ADD_HOOK(UIText_set_text, uiTextPtr);
}
else {
Log::InfoFmt("UIText_set_text: method not found, legacy UI.Text hook skipped.");
}
}
ADD_HOOK(TMP_Text_SetCharArray, Il2cppUtils::GetMethodPointer("Unity.TextMeshPro.dll", "TMPro",
"TMP_Text", "SetCharArray", {"System.Char[]", "System.Int32", "System.Int32"}));
/* SQL 查询相关函数,不好用 /* SQL 查询相关函数,不好用
// 下面是 byte[] u8 string 转 std::string 的例子 // 下面是 byte[] u8 string 转 std::string 的例子
auto query = reinterpret_cast<UnityResolve::UnityType::Array<UnityResolve::UnityType::Byte>*>(mtd); auto query = reinterpret_cast<UnityResolve::UnityType::Array<UnityResolve::UnityType::Byte>*>(mtd);
@ -1537,6 +1825,13 @@ namespace GakumasLocal::HookMain {
ADD_HOOK(PictureBookWindowPresenter_GetLiveMusics, ADD_HOOK(PictureBookWindowPresenter_GetLiveMusics,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame", Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookWindowPresenter", "GetLiveMusics")); "PictureBookWindowPresenter", "GetLiveMusics"));
#ifdef GKMS_WINDOWS
// 跳过切歌Loading安卓端会崩溃
ADD_HOOK(Produce_ViewPictureBookLiveAsync,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "",
"Produce", "ViewPictureBookLiveAsync"));
#endif
ADD_HOOK(PictureBookLiveSelectScreenModel_ctor, ADD_HOOK(PictureBookLiveSelectScreenModel_ctor,
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame", Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
"PictureBookLiveSelectScreenModel", ".ctor")); "PictureBookLiveSelectScreenModel", ".ctor"));
@ -1634,6 +1929,32 @@ namespace GakumasLocal::HookMain {
"RenderPipeline", "EndCameraRendering")); "RenderPipeline", "EndCameraRendering"));
#ifdef GKMS_WINDOWS #ifdef GKMS_WINDOWS
ADD_HOOK(WindowHandle_SetWindowLong, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow",
"WindowHandle", "SetWindowLong"));
//ADD_HOOK(WindowHandle_SetWindowLong32, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow",
// "WindowHandle", "SetWindowLong32"));
//ADD_HOOK(WindowHandle_SetWindowLongPtr64, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow",
// "WindowHandle", "SetWindowLongPtr64"));
//ADD_HOOK(WindowSizeUtility_RestoreWindowSize, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow",
// "WindowSizeUtility", "RestoreWindowSize"));
ADD_HOOK(WindowManager_ApplyOrientationSettings, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow",
"WindowManager", "ApplyOrientationSettings"));
ADD_HOOK(AspectRatioHandler_NudgeWindow, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow",
"AspectRatioHandler", "NudgeWindow"));
//ADD_HOOK(AspectRatioHandler_WindowProc, Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.Common.StandAloneWindow",
// "AspectRatioHandler", "WindowProc"));
if (GakumasLocal::Config::dmmUnlockSize) {
std::thread([]() {
std::this_thread::sleep_for(std::chrono::seconds(3));
auto hWnd = FindWindowW(L"UnityWndClass", L"gakumas");
// 添加可调整大小的边框和最大化按钮
LONG style = GetWindowLong(hWnd, GWL_STYLE);
style |= WS_THICKFRAME | WS_MAXIMIZEBOX;
SetWindowLong(hWnd, GWL_STYLE, style);
}).detach();
}
g_extra_assetbundle_paths.push_back((gakumasLocalPath / "local-files/gakumasassets").string()); g_extra_assetbundle_paths.push_back((gakumasLocalPath / "local-files/gakumasassets").string());
LoadExtraAssetBundle(); LoadExtraAssetBundle();
GkmsResourceUpdate::CheckUpdateFromAPI(false); GkmsResourceUpdate::CheckUpdateFromAPI(false);

View File

@ -337,17 +337,23 @@ namespace Il2cppUtils {
lst_get_Item_method = il2cpp_class_get_method_from_name(list_klass, "get_Item", 1); lst_get_Item_method = il2cpp_class_get_method_from_name(list_klass, "get_Item", 1);
lst_set_Item_method = il2cpp_class_get_method_from_name(list_klass, "set_Item", 2); lst_set_Item_method = il2cpp_class_get_method_from_name(list_klass, "set_Item", 2);
lst_Add_method = il2cpp_class_get_method_from_name(list_klass, "Add", 1); lst_Add_method = il2cpp_class_get_method_from_name(list_klass, "Add", 1);
lst_Contains_method = il2cpp_class_get_method_from_name(list_klass, "Contains", 1);
lst_get_Count = reinterpret_cast<lst_get_Count_t>(lst_get_Count_method->methodPointer); lst_get_Count = reinterpret_cast<lst_get_Count_t>(lst_get_Count_method->methodPointer);
lst_get_Item = reinterpret_cast<lst_get_Item_t>(lst_get_Item_method->methodPointer); lst_get_Item = reinterpret_cast<lst_get_Item_t>(lst_get_Item_method->methodPointer);
lst_set_Item = reinterpret_cast<lst_set_Item_t>(lst_set_Item_method->methodPointer); lst_set_Item = reinterpret_cast<lst_set_Item_t>(lst_set_Item_method->methodPointer);
lst_Add = reinterpret_cast<lst_Add_t>(lst_Add_method->methodPointer); lst_Add = reinterpret_cast<lst_Add_t>(lst_Add_method->methodPointer);
lst_Contains = reinterpret_cast<lst_Contains_t>(lst_Contains_method->methodPointer);
} }
void Add(T value) { void Add(T value) {
lst_Add(lst, value, lst_Add_method); lst_Add(lst, value, lst_Add_method);
} }
bool Contains(T value) {
return lst_Contains(lst, value, lst_Contains_method);
}
T get_Item(int index) { T get_Item(int index) {
return lst_get_Item(lst, index, lst_get_Item_method); return lst_get_Item(lst, index, lst_get_Item_method);
} }
@ -401,16 +407,86 @@ namespace Il2cppUtils {
typedef void(*lst_Add_t)(void*, T, void* mtd); typedef void(*lst_Add_t)(void*, T, void* mtd);
typedef void(*lst_set_Item_t)(void*, int, T, void* mtd); typedef void(*lst_set_Item_t)(void*, int, T, void* mtd);
typedef int(*lst_get_Count_t)(void*, void* mtd); typedef int(*lst_get_Count_t)(void*, void* mtd);
typedef bool(*lst_Contains_t)(void*, T, void* mtd);
MethodInfo* lst_get_Item_method; MethodInfo* lst_get_Item_method;
MethodInfo* lst_Add_method; MethodInfo* lst_Add_method;
MethodInfo* lst_get_Count_method; MethodInfo* lst_get_Count_method;
MethodInfo* lst_set_Item_method; MethodInfo* lst_set_Item_method;
MethodInfo* lst_Contains_method;
lst_get_Item_t lst_get_Item; lst_get_Item_t lst_get_Item;
lst_set_Item_t lst_set_Item; lst_set_Item_t lst_set_Item;
lst_Add_t lst_Add; lst_Add_t lst_Add;
lst_get_Count_t lst_get_Count; lst_get_Count_t lst_get_Count;
lst_Contains_t lst_Contains;
};
template <typename KT = void*, typename VT = void*>
class CSDictEditor {
public:
// @param dict: Dictionary instance.
// @param dictTypeStr: Reflection type. eg: "System.Collections.Generic.Dictionary`2[System.Int32, System.Int32]"
CSDictEditor(void* dict, const char* dictTypeStr) {
dic_klass = Il2cppUtils::get_system_class_from_reflection_type_str(dictTypeStr);
initDict(dict);
}
CSDictEditor(void* dict) {
dic_klass = get_class_from_instance(dict);
initDict(dict);
}
CSDictEditor(void* dict, void* dicClass) {
dic_klass = dicClass;
initDict(dict);
}
void Add(KT key, VT value) {
dic_Add(dict, key, value, Add_method);
}
bool ContainsKey(KT key) {
return dic_containsKey(dict, key, ContainsKey_method);
}
VT get_Item(KT key) {
return dic_get_Item(dict, key, get_Item_method);
}
VT operator[] (KT key) {
return get_Item(key);
}
void* dict;
void* dic_klass;
private:
void initDict(void* dict) {
// dic_klass = dicClass;
this->dict = dict;
get_Item_method = il2cpp_class_get_method_from_name(dic_klass, "get_Item", 1);
Add_method = il2cpp_class_get_method_from_name(dic_klass, "Add", 2);
ContainsKey_method = il2cpp_class_get_method_from_name(dic_klass, "ContainsKey", 1);
dic_get_Item = (dic_get_Item_t)get_Item_method->methodPointer;
dic_Add = (dic_Add_t)Add_method->methodPointer;
dic_containsKey = (dic_containsKey_t)ContainsKey_method->methodPointer;
}
typedef VT(*dic_get_Item_t)(void*, KT, void* mtd);
typedef VT(*dic_Add_t)(void*, KT, VT, void* mtd);
typedef VT(*dic_containsKey_t)(void*, KT, void* mtd);
CSDictEditor();
MethodInfo* get_Item_method;
MethodInfo* Add_method;
MethodInfo* ContainsKey_method;
dic_get_Item_t dic_get_Item;
dic_Add_t dic_Add;
dic_containsKey_t dic_containsKey;
}; };
} }

View File

@ -17,6 +17,8 @@
#include "BaseDefine.h" #include "BaseDefine.h"
#include "string_parser/StringParser.hpp" #include "string_parser/StringParser.hpp"
// #include "cpprest/details/http_helpers.h"
namespace GakumasLocal::Local { namespace GakumasLocal::Local {
std::unordered_map<std::string, std::string> i18nData{}; std::unordered_map<std::string, std::string> i18nData{};
@ -37,6 +39,12 @@ namespace GakumasLocal::Local {
return Plugin::GetInstance().GetHookInstaller()->localizationFilesDir; return Plugin::GetInstance().GetHookInstaller()->localizationFilesDir;
} }
bool isAllSpace(const std::string& str) {
return std::all_of(str.begin(), str.end(), [](unsigned char c) {
return std::isspace(c);
});
}
std::string trim(const std::string& str) { std::string trim(const std::string& str) {
auto is_not_space = [](char ch) { return !std::isspace(ch); }; auto is_not_space = [](char ch) { return !std::isspace(ch); };
auto start = std::ranges::find_if(str, is_not_space); auto start = std::ranges::find_if(str, is_not_space);
@ -249,7 +257,7 @@ namespace GakumasLocal::Local {
} }
bool GetSplitTagsTranslation(const std::string& origText, std::string* newText, std::vector<std::string>& unTransResultRet) { bool GetSplitTagsTranslation(const std::string& origText, std::string* newText, std::vector<std::string>& unTransResultRet) {
if (!origText.contains(L'<')) return false; if (!origText.contains('<')) return false;
const auto splitResult = SplitByTags(origText); const auto splitResult = SplitByTags(origText);
if (splitResult.empty()) return false; if (splitResult.empty()) return false;
@ -289,10 +297,18 @@ namespace GakumasLocal::Local {
std::u16string currentWaitingReplaceText; std::u16string currentWaitingReplaceText;
#ifdef GKMS_WINDOWS
#define checkCurrentWaitingReplaceTextAndClear() \
if (!currentWaitingReplaceText.empty()) { \
auto trimmed = trim(Misc::ToUTF8(currentWaitingReplaceText)); \
waitingReplaceTexts.push_back(trimmed); \
currentWaitingReplaceText.clear(); }
#else
#define checkCurrentWaitingReplaceTextAndClear() \ #define checkCurrentWaitingReplaceTextAndClear() \
if (!currentWaitingReplaceText.empty()) { \ if (!currentWaitingReplaceText.empty()) { \
waitingReplaceTexts.push_back(Misc::ToUTF8(currentWaitingReplaceText)); \ waitingReplaceTexts.push_back(Misc::ToUTF8(currentWaitingReplaceText)); \
currentWaitingReplaceText.clear(); } currentWaitingReplaceText.clear(); }
#endif
for (char16_t currChar : origText) { for (char16_t currChar : origText) {
if (currChar == u'<') { if (currChar == u'<') {
@ -333,6 +349,7 @@ namespace GakumasLocal::Local {
bool hasNotTrans = false; bool hasNotTrans = false;
if (!waitingReplaceTexts.empty()) { if (!waitingReplaceTexts.empty()) {
for (const auto& i : waitingReplaceTexts) { for (const auto& i : waitingReplaceTexts) {
if (isAllSpace(i)) continue;
std::string searchResult = findInMapIgnoreSpace(i, genericSplitText); std::string searchResult = findInMapIgnoreSpace(i, genericSplitText);
if (!searchResult.empty()) { if (!searchResult.empty()) {
ReplaceNumberComma(&searchResult); ReplaceNumberComma(&searchResult);

View File

@ -1,11 +1,12 @@
#include "Misc.hpp" #include "Misc.hpp"
#include <codecvt>
#include <locale>
#include "fmt/core.h" #include "fmt/core.h"
#ifndef GKMS_WINDOWS #ifndef GKMS_WINDOWS
#include <jni.h> #include <jni.h>
#if defined(__ANDROID__) && __ANDROID_API__ >= 31
#include <unicode/ustring.h>
#endif
extern JavaVM* g_javaVM; extern JavaVM* g_javaVM;
#else #else
@ -14,20 +15,155 @@
namespace GakumasLocal::Misc { namespace GakumasLocal::Misc {
std::u16string ToUTF16(const std::string_view& str) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
return utf16conv.from_bytes(str.data(), str.data() + str.size());
}
std::string ToUTF8(const std::u16string_view& str) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv;
return utf16conv.to_bytes(str.data(), str.data() + str.size());
}
#ifdef GKMS_WINDOWS #ifdef GKMS_WINDOWS
std::string ToUTF8(const std::wstring_view& str) { std::string ToUTF8(const std::wstring_view& str) {
return utility::conversions::to_utf8string(str.data()); return utility::conversions::to_utf8string(str.data());
} }
std::u16string ToUTF16(const std::string_view& str) {
std::string input(str);
std::wstring wstr = utility::conversions::utf8_to_utf16(input);
return std::u16string(wstr.begin(), wstr.end());
}
std::string ToUTF8(const std::u16string_view& str) {
std::u16string u16(str);
std::wstring wstr(u16.begin(), u16.end());
return utility::conversions::utf16_to_utf8(wstr);
}
#else
namespace {
jclass GetStringClass(JNIEnv* env) {
static jclass stringClass = nullptr;
if (stringClass) return stringClass;
jclass localClass = env->FindClass("java/lang/String");
if (!localClass) return nullptr;
stringClass = reinterpret_cast<jclass>(env->NewGlobalRef(localClass));
env->DeleteLocalRef(localClass);
return stringClass;
}
jstring GetUtf8CharsetName(JNIEnv* env) {
static jstring utf8Charset = nullptr;
if (utf8Charset) return utf8Charset;
jstring localUtf8 = env->NewStringUTF("UTF-8");
if (!localUtf8) return nullptr;
utf8Charset = reinterpret_cast<jstring>(env->NewGlobalRef(localUtf8));
env->DeleteLocalRef(localUtf8);
return utf8Charset;
}
}
std::u16string ToUTF16(const std::string_view& str) {
#if defined(__ANDROID__) && __ANDROID_API__ >= 31
UErrorCode status = U_ZERO_ERROR;
int32_t outLen = 0;
u_strFromUTF8(nullptr, 0, &outLen, str.data(), static_cast<int32_t>(str.size()), &status);
if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) return {};
status = U_ZERO_ERROR;
std::u16string out(outLen, u'\0');
u_strFromUTF8(
reinterpret_cast<UChar*>(out.data()),
outLen,
&outLen,
str.data(),
static_cast<int32_t>(str.size()),
&status);
if (U_FAILURE(status)) return {};
out.resize(outLen);
return out;
#else
JNIEnv* env = GetJNIEnv();
if (!env) return {};
jclass stringClass = GetStringClass(env);
jstring utf8Charset = GetUtf8CharsetName(env);
if (!stringClass || !utf8Charset) return {};
jmethodID ctor = env->GetMethodID(stringClass, "<init>", "([BLjava/lang/String;)V");
if (!ctor) return {};
jbyteArray bytes = env->NewByteArray(static_cast<jsize>(str.size()));
if (!bytes) return {};
env->SetByteArrayRegion(bytes, 0, static_cast<jsize>(str.size()), reinterpret_cast<const jbyte*>(str.data()));
jstring jstr = reinterpret_cast<jstring>(env->NewObject(stringClass, ctor, bytes, utf8Charset));
env->DeleteLocalRef(bytes);
if (!jstr || env->ExceptionCheck()) {
env->ExceptionClear();
if (jstr) env->DeleteLocalRef(jstr);
return {};
}
const jsize len = env->GetStringLength(jstr);
const jchar* chars = env->GetStringChars(jstr, nullptr);
if (!chars) {
env->DeleteLocalRef(jstr);
return {};
}
std::u16string out(reinterpret_cast<const char16_t*>(chars), reinterpret_cast<const char16_t*>(chars) + len);
env->ReleaseStringChars(jstr, chars);
env->DeleteLocalRef(jstr);
return out;
#endif
}
std::string ToUTF8(const std::u16string_view& str) {
#if defined(__ANDROID__) && __ANDROID_API__ >= 31
UErrorCode status = U_ZERO_ERROR;
int32_t outLen = 0;
u_strToUTF8(nullptr, 0, &outLen, reinterpret_cast<const UChar*>(str.data()), static_cast<int32_t>(str.size()), &status);
if (status != U_BUFFER_OVERFLOW_ERROR && U_FAILURE(status)) return {};
status = U_ZERO_ERROR;
std::string out(outLen, '\0');
u_strToUTF8(
out.data(),
outLen,
&outLen,
reinterpret_cast<const UChar*>(str.data()),
static_cast<int32_t>(str.size()),
&status);
if (U_FAILURE(status)) return {};
out.resize(outLen);
return out;
#else
JNIEnv* env = GetJNIEnv();
if (!env) return {};
jclass stringClass = GetStringClass(env);
jstring utf8Charset = GetUtf8CharsetName(env);
if (!stringClass || !utf8Charset) return {};
jstring jstr = env->NewString(reinterpret_cast<const jchar*>(str.data()), static_cast<jsize>(str.size()));
if (!jstr) return {};
jmethodID getBytes = env->GetMethodID(stringClass, "getBytes", "(Ljava/lang/String;)[B");
if (!getBytes) {
env->DeleteLocalRef(jstr);
return {};
}
jbyteArray bytes = reinterpret_cast<jbyteArray>(env->CallObjectMethod(jstr, getBytes, utf8Charset));
env->DeleteLocalRef(jstr);
if (!bytes || env->ExceptionCheck()) {
env->ExceptionClear();
if (bytes) env->DeleteLocalRef(bytes);
return {};
}
const jsize len = env->GetArrayLength(bytes);
std::string out(static_cast<size_t>(len), '\0');
env->GetByteArrayRegion(bytes, 0, len, reinterpret_cast<jbyte*>(out.data()));
env->DeleteLocalRef(bytes);
return out;
#endif
}
#endif #endif
#ifndef GKMS_WINDOWS #ifndef GKMS_WINDOWS

View File

@ -54,6 +54,8 @@ namespace GakumasLocal::Config {
float bLimitZx = 1.0f; float bLimitZx = 1.0f;
float bLimitZy = 1.0f; float bLimitZy = 1.0f;
bool dmmUnlockSize = false;
void LoadConfig(const std::string& configStr) { void LoadConfig(const std::string& configStr) {
try { try {
const auto config = nlohmann::json::parse(configStr); const auto config = nlohmann::json::parse(configStr);
@ -102,6 +104,7 @@ namespace GakumasLocal::Config {
GetConfigItem(bLimitYy); GetConfigItem(bLimitYy);
GetConfigItem(bLimitZx); GetConfigItem(bLimitZx);
GetConfigItem(bLimitZy); GetConfigItem(bLimitZy);
GetConfigItem(dmmUnlockSize);
} }
catch (std::exception& e) { catch (std::exception& e) {
Log::ErrorFmt("LoadConfig error: %s", e.what()); Log::ErrorFmt("LoadConfig error: %s", e.what());
@ -157,6 +160,7 @@ namespace GakumasLocal::Config {
SetConfigItem(bLimitYy); SetConfigItem(bLimitYy);
SetConfigItem(bLimitZx); SetConfigItem(bLimitZx);
SetConfigItem(bLimitZy); SetConfigItem(bLimitZy);
SetConfigItem(dmmUnlockSize);
std::ofstream out(configPath); std::ofstream out(configPath);
if (!out) { if (!out) {

View File

@ -51,6 +51,8 @@ namespace GakumasLocal::Config {
extern float bLimitZx; extern float bLimitZx;
extern float bLimitZy; extern float bLimitZy;
extern bool dmmUnlockSize;
void LoadConfig(const std::string& configStr); void LoadConfig(const std::string& configStr);
void SaveConfig(const std::string& configPath); void SaveConfig(const std::string& configPath);
} }

View File

@ -565,10 +565,10 @@ public:
} }
catch (...) { catch (...) {
std::cout << funcName << " Invoke Error\n"; std::cout << funcName << " Invoke Error\n";
Return(); return Return();
} }
} }
Return(); return Return();
} }
inline static std::vector<Assembly*> assembly; inline static std::vector<Assembly*> assembly;

View File

@ -3,7 +3,7 @@ package io.github.chinosk.gakumas.localify
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.app.AndroidAppHelper import android.app.Application
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
@ -17,34 +17,32 @@ import android.view.MotionEvent
import android.widget.Toast import android.widget.Toast
import com.bytedance.shadowhook.ShadowHook import com.bytedance.shadowhook.ShadowHook
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder import com.bytedance.shadowhook.ShadowHook.ConfigBuilder
import de.robv.android.xposed.IXposedHookLoadPackage import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.localizationFilesDir
import io.github.chinosk.gakumas.localify.mainUtils.json
import io.github.chinosk.gakumas.localify.models.GakumasConfig import io.github.chinosk.gakumas.localify.models.GakumasConfig
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
import io.github.libxposed.api.XposedInterface
import io.github.libxposed.api.XposedModule
import io.github.libxposed.api.XposedModuleInterface
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.lang.reflect.Method
import java.util.Locale 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.FilesChecker.localizationFilesDir
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" val TAG = "GakumasLocalify"
class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit { class GakumasHookMain : XposedModule() {
private lateinit var modulePath: String private var modulePath: String = ""
private var nativeLibLoadSuccess: Boolean private var nativeLibLoadSuccess: Boolean = false
private var alreadyInitialized = false private var alreadyInitialized = false
private val targetPackageName = "com.bandainamcoent.idolmaster_gakuen" private val targetPackageName = "com.bandainamcoent.idolmaster_gakuen"
private val nativeLibName = "MarryKotone" private val nativeLibName = "MarryKotone"
@ -55,160 +53,197 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
private var externalFilesChecked: Boolean = false private var externalFilesChecked: Boolean = false
private var gameActivity: Activity? = null private var gameActivity: Activity? = null
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) { override fun onModuleLoaded(param: XposedModuleInterface.ModuleLoadedParam) {
// if (lpparam.packageName == "io.github.chinosk.gakumas.localify") { modulePath = getModuleApplicationInfo().sourceDir
// XposedHelpers.findAndHookMethod(
// "io.github.chinosk.gakumas.localify.MainActivity",
// lpparam.classLoader,
// "showToast",
// String::class.java,
// object : XC_MethodHook() {
// override fun beforeHookedMethod(param: MethodHookParam) {
// Log.d(TAG, "beforeHookedMethod hooked: ${param.args}")
// }
// }
// )
// }
if (lpparam.packageName != targetPackageName) { ShadowHook.init(
ConfigBuilder()
.setMode(ShadowHook.Mode.UNIQUE)
.build()
)
nativeLibLoadSuccess = try {
System.loadLibrary(nativeLibName)
true
} catch (_: UnsatisfiedLinkError) {
false
}
}
override fun onPackageReady(param: XposedModuleInterface.PackageReadyParam) {
if (param.packageName != targetPackageName) {
return return
} }
XposedHelpers.findAndHookMethod( val classLoader = param.classLoader
"android.app.Activity",
lpparam.classLoader, hookMethod(
"dispatchKeyEvent", classLoader = classLoader,
KeyEvent::class.java, className = "android.app.Activity",
object : XC_MethodHook() { methodName = "dispatchKeyEvent",
override fun beforeHookedMethod(param: MethodHookParam) { parameterTypes = arrayOf(KeyEvent::class.java),
val keyEvent = param.args[0] as KeyEvent before = { chain ->
val keyCode = keyEvent.keyCode val keyEvent = chain.getArg(0) as KeyEvent
val action = keyEvent.action keyboardEvent(keyEvent.keyCode, keyEvent.action)
// Log.d(TAG, "Key event: keyCode=$keyCode, action=$action")
keyboardEvent(keyCode, action)
}
} }
) )
XposedHelpers.findAndHookMethod( hookMethod(
"android.app.Activity", classLoader = classLoader,
lpparam.classLoader, className = "android.app.Activity",
"dispatchGenericMotionEvent", methodName = "dispatchGenericMotionEvent",
MotionEvent::class.java, parameterTypes = arrayOf(MotionEvent::class.java),
object : XC_MethodHook() { before = { chain ->
override fun beforeHookedMethod(param: MethodHookParam) { val motionEvent = chain.getArg(0) as MotionEvent
val motionEvent = param.args[0] as MotionEvent val action = motionEvent.action
val action = motionEvent.action
// 左摇杆的X和Y轴 // 左摇杆的X和Y轴
val leftStickX = motionEvent.getAxisValue(MotionEvent.AXIS_X) val leftStickX = motionEvent.getAxisValue(MotionEvent.AXIS_X)
val leftStickY = motionEvent.getAxisValue(MotionEvent.AXIS_Y) val leftStickY = motionEvent.getAxisValue(MotionEvent.AXIS_Y)
// 右摇杆的X和Y轴 // 右摇杆的X和Y轴
val rightStickX = motionEvent.getAxisValue(MotionEvent.AXIS_Z) val rightStickX = motionEvent.getAxisValue(MotionEvent.AXIS_Z)
val rightStickY = motionEvent.getAxisValue(MotionEvent.AXIS_RZ) val rightStickY = motionEvent.getAxisValue(MotionEvent.AXIS_RZ)
// 左扳机 // 左扳机
val leftTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_LTRIGGER) val leftTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_LTRIGGER)
// 右扳机 // 右扳机
val rightTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_RTRIGGER) val rightTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_RTRIGGER)
// 十字键 // 十字键
val hatX = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X) val hatX = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X)
val hatY = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y) val hatY = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y)
// 处理摇杆和扳机事件 // 处理摇杆和扳机事件
joystickEvent( joystickEvent(
action, action,
leftStickX, leftStickX,
leftStickY, leftStickY,
rightStickX, rightStickX,
rightStickY, rightStickY,
leftTrigger, leftTrigger,
rightTrigger, rightTrigger,
hatX, hatX,
hatY hatY
) )
}
} }
) )
val appActivityClass = XposedHelpers.findClass("android.app.Activity", lpparam.classLoader) val activityClass = classLoader.loadClass("android.app.Activity")
XposedBridge.hookAllMethods(appActivityClass, "onStart", object : XC_MethodHook() { hookAllMethods(activityClass, "onStart") { chain ->
override fun beforeHookedMethod(param: MethodHookParam) { Log.d(TAG, "onStart")
super.beforeHookedMethod(param) val currActivity = chain.thisObject as Activity
Log.d(TAG, "onStart") gameActivity = currActivity
val currActivity = param.thisObject as Activity if (getConfigError != null) {
gameActivity = currActivity showGetConfigFailed(currActivity)
if (getConfigError != null) { } else {
showGetConfigFailed(currActivity) initGkmsConfig(currActivity)
}
else {
initGkmsConfig(currActivity)
}
} }
}) chain.proceed()
}
XposedBridge.hookAllMethods(appActivityClass, "onResume", object : XC_MethodHook() { hookAllMethods(activityClass, "onResume") { chain ->
override fun beforeHookedMethod(param: MethodHookParam) { Log.d(TAG, "onResume")
Log.d(TAG, "onResume") val currActivity = chain.thisObject as Activity
val currActivity = param.thisObject as Activity gameActivity = currActivity
gameActivity = currActivity if (getConfigError != null) {
if (getConfigError != null) { showGetConfigFailed(currActivity)
showGetConfigFailed(currActivity) } else {
} initGkmsConfig(currActivity)
else {
initGkmsConfig(currActivity)
}
} }
}) chain.proceed()
}
val cls = lpparam.classLoader.loadClass("com.unity3d.player.UnityPlayer") val unityPlayerClass = classLoader.loadClass("com.unity3d.player.UnityPlayer")
XposedHelpers.findAndHookMethod( val loadNativeMethod = unityPlayerClass.getDeclaredMethod("loadNative", String::class.java)
cls,
"loadNative",
String::class.java,
object : XC_MethodHook() {
@SuppressLint("UnsafeDynamicallyLoadedCode")
override fun afterHookedMethod(param: MethodHookParam) {
super.afterHookedMethod(param)
Log.i(TAG, "UnityPlayer.loadNative") hook(loadNativeMethod).intercept { chain ->
val result = chain.proceed()
if (alreadyInitialized) { onUnityLoadNativeAfterHook()
return result
} }
val app = AndroidAppHelper.currentApplication()
if (nativeLibLoadSuccess) {
showToast("lib$nativeLibName.so loaded.")
}
else {
showToast("Load native library lib$nativeLibName.so failed.")
return
}
if (!gkmsDataInited) {
requestConfig(app.applicationContext)
}
FilesChecker.initDir(app.filesDir, modulePath)
initHook(
"${app.applicationInfo.nativeLibraryDir}/libil2cpp.so",
File(
app.filesDir.absolutePath,
FilesChecker.localizationFilesDir
).absolutePath
)
alreadyInitialized = true
}
})
startLoop() startLoop()
} }
private fun hookMethod(
classLoader: ClassLoader,
className: String,
methodName: String,
parameterTypes: Array<Class<*>>,
before: ((XposedInterface.Chain) -> Unit)? = null,
after: ((XposedInterface.Chain, Any?) -> Unit)? = null,
) {
val clazz = classLoader.loadClass(className)
val method = clazz.getDeclaredMethod(methodName, *parameterTypes)
hook(method).intercept { chain ->
before?.invoke(chain)
val result = chain.proceed()
after?.invoke(chain, result)
result
}
}
private fun hookAllMethods(clazz: Class<*>, methodName: String, interceptor: (XposedInterface.Chain) -> Any?) {
val allMethods = (clazz.declaredMethods.asSequence() + clazz.methods.asSequence())
.filter { it.name == methodName }
.distinctBy(Method::toGenericString)
.toList()
allMethods.forEach { method ->
hook(method).intercept { chain -> interceptor(chain) }
}
}
@SuppressLint("UnsafeDynamicallyLoadedCode")
private fun onUnityLoadNativeAfterHook() {
Log.i(TAG, "UnityPlayer.loadNative")
if (alreadyInitialized) {
return
}
val app = getCurrentApplication()
if (app == null) {
Log.e(TAG, "currentApplication is null")
return
}
if (nativeLibLoadSuccess) {
showToast("lib$nativeLibName.so loaded.")
} else {
showToast("Load native library lib$nativeLibName.so failed.")
return
}
if (!gkmsDataInited) {
requestConfig(app.applicationContext)
}
FilesChecker.initDir(app.filesDir, modulePath)
initHook(
"${app.applicationInfo.nativeLibraryDir}/libil2cpp.so",
File(
app.filesDir.absolutePath,
FilesChecker.localizationFilesDir
).absolutePath
)
alreadyInitialized = true
}
private fun getCurrentApplication(): Application? {
return try {
val activityThreadClass = Class.forName("android.app.ActivityThread")
val method = activityThreadClass.getDeclaredMethod("currentApplication")
method.isAccessible = true
method.invoke(null) as? Application
} catch (_: Throwable) {
null
}
}
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
private fun startLoop() { private fun startLoop() {
GlobalScope.launch { GlobalScope.launch {
@ -231,8 +266,7 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
if ((gameActivity != null) && (lastFrameStartInit != NativeInitProgress.startInit)) { // change status if ((gameActivity != null) && (lastFrameStartInit != NativeInitProgress.startInit)) { // change status
if (NativeInitProgress.startInit) { if (NativeInitProgress.startInit) {
initProgressUI.createView(gameActivity!!) initProgressUI.createView(gameActivity!!)
} } else {
else {
initProgressUI.finishLoad(gameActivity!!) initProgressUI.finishLoad(gameActivity!!)
} }
} }
@ -286,7 +320,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
// 使用热更新文件 // 使用热更新文件
if ((programConfig?.useRemoteAssets == true) || (programConfig?.useAPIAssets == true)) { if ((programConfig?.useRemoteAssets == true) || (programConfig?.useAPIAssets == true)) {
// val dataUri = intent.data
val dataUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val dataUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra("resource_file", Uri::class.java) intent.getParcelableExtra("resource_file", Uri::class.java)
} else { } else {
@ -297,7 +330,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
if (dataUri != null) { if (dataUri != null) {
if (!externalFilesChecked) { if (!externalFilesChecked) {
externalFilesChecked = true externalFilesChecked = true
// Log.d(TAG, "dataUri: $dataUri")
FileHotUpdater.updateFilesFromZip(activity, dataUri, activity.filesDir, FileHotUpdater.updateFilesFromZip(activity, dataUri, activity.filesDir,
programConfig.delRemoteAfterUpdate) programConfig.delRemoteAfterUpdate)
} }
@ -444,10 +476,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
} }
override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
modulePath = startupParam.modulePath
}
companion object { companion object {
@JvmStatic @JvmStatic
external fun initHook(targetLibraryPath: String, localizationFilesDir: String) external fun initHook(targetLibraryPath: String, localizationFilesDir: String)
@ -473,7 +501,15 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
@JvmStatic @JvmStatic
fun showToast(message: String) { fun showToast(message: String) {
val app = AndroidAppHelper.currentApplication() val app = try {
val activityThreadClass = Class.forName("android.app.ActivityThread")
val method = activityThreadClass.getDeclaredMethod("currentApplication")
method.isAccessible = true
method.invoke(null) as? Application
} catch (_: Throwable) {
null
}
val context = app?.applicationContext val context = app?.applicationContext
if (context != null) { if (context != null) {
val handler = Handler(Looper.getMainLooper()) val handler = Handler(Looper.getMainLooper())
@ -494,19 +530,4 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
@JvmStatic @JvmStatic
external fun pluginCallbackLooper(): Int external fun pluginCallbackLooper(): Int
} }
}
init {
ShadowHook.init(
ConfigBuilder()
.setMode(ShadowHook.Mode.UNIQUE)
.build()
)
nativeLibLoadSuccess = try {
System.loadLibrary(nativeLibName)
true
} catch (e: UnsatisfiedLinkError) {
false
}
}
}

View File

@ -48,8 +48,11 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
} }
fun gotoPatchActivity() { fun gotoPatchActivity() {
val intent = Intent(this, PatchActivity::class.java) mainUIConfirmStatUpdate(
startActivity(intent) isShow = true,
title = getString(R.string.patcher_unavailable_title),
content = getString(R.string.patcher_unavailable_content)
)
} }
override fun saveConfig() { override fun saveConfig() {

View File

@ -1,15 +1,16 @@
package io.github.chinosk.gakumas.localify.hookUtils package io.github.chinosk.gakumas.localify.hookUtils
import android.content.res.XModuleResources
import android.util.Log import android.util.Log
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.zip.ZipFile
object FilesChecker { object FilesChecker {
private const val MODULE_ASSETS_PREFIX = "assets/"
lateinit var filesDir: File lateinit var filesDir: File
lateinit var modulePath: String lateinit var modulePath: String
val localizationFilesDir = "gakumas-local" val localizationFilesDir = "gakumas-local"
@ -17,7 +18,6 @@ object FilesChecker {
fun initAndCheck(fileDir: File, modulePath: String) { fun initAndCheck(fileDir: File, modulePath: String) {
initDir(fileDir, modulePath) initDir(fileDir, modulePath)
checkFiles() checkFiles()
} }
@ -46,31 +46,28 @@ object FilesChecker {
pluginBasePath.mkdirs() pluginBasePath.mkdirs()
} }
val assets = XModuleResources.createInstance(modulePath, null).assets val rootAssetDir = moduleAssetPath(localizationFilesDir).trimEnd('/') + "/"
fun forAllAssetFiles( ZipFile(modulePath).use { zipFile ->
basePath: String, val entries = zipFile.entries()
action: (String, InputStream?) -> Unit while (entries.hasMoreElements()) {
) { val entry = entries.nextElement()
val assetFiles = assets.list(basePath)!! val name = entry.name
for (file in assetFiles) { if (!name.startsWith(rootAssetDir)) continue
try {
assets.open("$basePath/$file") val relativePath = name.removePrefix(MODULE_ASSETS_PREFIX)
} catch (e: IOException) { if (relativePath.isBlank()) continue
action("$basePath/$file", null)
forAllAssetFiles("$basePath/$file", action) val outFile = File(filesDir, relativePath)
if (entry.isDirectory) {
outFile.mkdirs()
continue continue
}.use {
action("$basePath/$file", it)
} }
}
} outFile.parentFile?.mkdirs()
forAllAssetFiles(localizationFilesDir) { path, file -> zipFile.getInputStream(entry).use { input ->
val outFile = File(filesDir, path) outFile.outputStream().use { output ->
if (file == null) { input.copyTo(output)
outFile.mkdirs() }
} else {
outFile.outputStream().use { out ->
file.copyTo(out)
} }
} }
} }
@ -79,15 +76,13 @@ object FilesChecker {
} }
fun getPluginVersion(): String { fun getPluginVersion(): String {
val assets = XModuleResources.createInstance(modulePath, null).assets val versionAssetPath = moduleAssetPath("$localizationFilesDir/version.txt")
ZipFile(modulePath).use { zipFile ->
for (i in assets.list(localizationFilesDir)!!) { val entry = zipFile.getEntry(versionAssetPath) ?: return "0.0"
if (i.toString() == "version.txt") { zipFile.getInputStream(entry).use { stream ->
val stream = assets.open("$localizationFilesDir/$i")
return convertToString(stream).trim() return convertToString(stream).trim()
} }
} }
return "0.0"
} }
fun getInstalledVersion(): String { fun getInstalledVersion(): String {
@ -100,26 +95,25 @@ object FilesChecker {
} }
fun convertToString(inputStream: InputStream?): String { fun convertToString(inputStream: InputStream?): String {
val stringBuilder = StringBuilder() if (inputStream == null) return ""
var reader: BufferedReader? = null return try {
try { BufferedReader(InputStreamReader(inputStream)).use { reader ->
reader = BufferedReader(InputStreamReader(inputStream)) buildString {
var line: String? var line: String?
while (reader.readLine().also { line = it } != null) { while (reader.readLine().also { line = it } != null) {
stringBuilder.append(line) append(line)
}
}
} }
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
} finally { ""
if (reader != null) {
try {
reader.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
} }
return stringBuilder.toString() }
private fun moduleAssetPath(path: String): String {
val cleanPath = path.trimStart('/')
return "$MODULE_ASSETS_PREFIX$cleanPath"
} }
private fun deleteRecursively(file: File): Boolean { private fun deleteRecursively(file: File): Boolean {
@ -167,4 +161,4 @@ object FilesChecker {
i18nFile.writeText("{}") i18nFile.writeText("{}")
} }
} }
} }

View File

@ -365,6 +365,8 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
checked = config.value.unlockAllLiveCostume) { checked = config.value.unlockAllLiveCostume) {
v -> context?.onUnlockAllLiveCostumeChanged(v) v -> context?.onUnlockAllLiveCostumeChanged(v)
} }
/*
HorizontalDivider( HorizontalDivider(
thickness = 1.dp, thickness = 1.dp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f) color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
@ -389,7 +391,7 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
value = config.value.liveCustomeCostumeId, value = config.value.liveCustomeCostumeId,
onValueChange = { c -> context?.onLiveCustomeCostumeIdChanged(c, 0, 0, 0)}, onValueChange = { c -> context?.onLiveCustomeCostumeIdChanged(c, 0, 0, 0)},
label = { Text(stringResource(R.string.live_custome_dress_id)) } label = { Text(stringResource(R.string.live_custome_dress_id)) }
) )*/
} }
} }
} }

View File

@ -299,6 +299,8 @@ Xposed スコープは再パッチなしで動的に変更が可能です。
<string name="patch_uninstall_confirm">アンインストールをしてもよろしいですか?</string> <string name="patch_uninstall_confirm">アンインストールをしてもよろしいですか?</string>
<string name="patch_uninstall_text">"署名が異なるため、パッチをインストールする前に元となるアプリをアンインストールする必要があります。 <string name="patch_uninstall_text">"署名が異なるため、パッチをインストールする前に元となるアプリをアンインストールする必要があります。
個人データのバックアップを設定済みであることを確認してください。"</string> 個人データのバックアップを設定済みであることを確認してください。"</string>
<string name="patcher_unavailable_title">Patcher は利用できません</string>
<string name="patcher_unavailable_content">GKMSPatchを使用して、root権限なしでご利用ください</string>
<string name="path_password_eye">M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z</string> <string name="path_password_eye">M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z</string>
<string name="path_password_eye_mask_strike_through">M2,4.27 L19.73,22 L22.27,19.46 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string> <string name="path_password_eye_mask_strike_through">M2,4.27 L19.73,22 L22.27,19.46 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string>
<string name="path_password_eye_mask_visible">M2,4.27 L2,4.27 L4.54,1.73 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string> <string name="path_password_eye_mask_visible">M2,4.27 L2,4.27 L4.54,1.73 L4.54,1.73 L4.54,1 L23,1 L23,23 L1,23 L1,4.27 Z</string>

View File

@ -102,7 +102,9 @@
<string name="patch_uninstall_text">由于签名不同,安装修补的应用前需要先卸载原应用。\n确保您已备份好个人数据。</string> <string name="patch_uninstall_text">由于签名不同,安装修补的应用前需要先卸载原应用。\n确保您已备份好个人数据。</string>
<string name="patch_uninstall_confirm">您确定要卸载吗</string> <string name="patch_uninstall_confirm">您确定要卸载吗</string>
<string name="patch_finished">修补完成,是否开始安装?</string> <string name="patch_finished">修补完成,是否开始安装?</string>
<string name="patcher_unavailable_title">Patcher 暂不可用</string>
<string name="patcher_unavailable_content">请使用GKMSPatch进行免Root使用</string>
<string name="about_contributors_asset_file">about_contributors_zh_cn.json</string> <string name="about_contributors_asset_file">about_contributors_zh_cn.json</string>
<string name="default_assets_check_api">https://uma.chinosk6.cn/api/gkms_trans_data</string> <string name="default_assets_check_api">https://uma.chinosk6.cn/api/gkms_trans_data</string>
</resources> </resources>

View File

@ -48,7 +48,7 @@
<string name="axisx_y">axisX.y</string> <string name="axisx_y">axisX.y</string>
<string name="axisy_y">axisY.y</string> <string name="axisy_y">axisY.y</string>
<string name="axisz_y">axisZ.y</string> <string name="axisz_y">axisZ.y</string>
<string name="basic_settings">Basic Ssettings</string> <string name="basic_settings">Basic Settings</string>
<string name="graphic_settings">Graphic Settings</string> <string name="graphic_settings">Graphic Settings</string>
<string name="camera_settings">Camera Settings</string> <string name="camera_settings">Camera Settings</string>
<string name="test_mode_live">Test Mode - LIVE</string> <string name="test_mode_live">Test Mode - LIVE</string>
@ -102,7 +102,9 @@
<string name="patch_uninstall_text">Due to different signatures, you need to uninstall the original app before installing the patched one.\nMake sure you have backed up personal data.</string> <string name="patch_uninstall_text">Due to different signatures, you need to uninstall the original app before installing the patched one.\nMake sure you have backed up personal data.</string>
<string name="patch_uninstall_confirm">Are you sure you want to uninstall?</string> <string name="patch_uninstall_confirm">Are you sure you want to uninstall?</string>
<string name="patch_finished">Patch finished. Start installing?</string> <string name="patch_finished">Patch finished. Start installing?</string>
<string name="patcher_unavailable_title">Patcher Unavailable</string>
<string name="patcher_unavailable_content">Please use GKMSPatch to use this module rootlessly</string>
<string name="about_contributors_asset_file">about_contributors_en.json</string> <string name="about_contributors_asset_file">about_contributors_en.json</string>
<string name="default_assets_check_api">https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest</string> <string name="default_assets_check_api">https://api.github.com/repos/NatsumeLS/Gakumas-Translation-Data-EN/releases/latest</string>
</resources> </resources>

View File

@ -0,0 +1 @@
io.github.chinosk.gakumas.localify.GakumasHookMain

View File

@ -0,0 +1,3 @@
minApiVersion=101
targetApiVersion=101
staticScope=true

View File

@ -0,0 +1 @@
com.bandainamcoent.idolmaster_gakuen

View File

@ -6,7 +6,7 @@ shizukuApi = "12.1.0"
hiddenapi-refine = "4.3.0" hiddenapi-refine = "4.3.0"
hiddenapi-stub = "4.2.0" hiddenapi-stub = "4.2.0"
okhttpBom = "4.12.0" okhttpBom = "4.12.0"
xposedApi = "82" libxposedApi = "101.0.0"
appcompat = "1.7.0" appcompat = "1.7.0"
coil = "2.6.0" coil = "2.6.0"
composeBom = "2024.06.00" composeBom = "2024.06.00"
@ -50,7 +50,7 @@ rikka-hidden-stub = { module = "dev.rikka.hidden:stub", version.ref = "hiddenapi
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" } logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" }
okhttp = { module = "com.squareup.okhttp3:okhttp" } okhttp = { module = "com.squareup.okhttp3:okhttp" }
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" } okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" }
xposed-api = { module = "de.robv.android.xposed:api", version.ref = "xposedApi" } libxposed-api = { module = "io.github.libxposed:api", version.ref = "libxposedApi" }
coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" } coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
material = { module = "com.google.android.material:material", version.ref = "material" } material = { module = "com.google.android.material:material", version.ref = "material" }

View File

@ -18,7 +18,6 @@ dependencyResolutionManagement {
google() google()
mavenCentral() mavenCentral()
maven { url "https://api.xposed.info/" }
} }
} }