Compare commits

...

32 Commits
v1.6.3 ... main

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
chinosk d09904643f
fixed crash 2025-03-18 09:28:15 +00:00
chinosk d83854c755
Adapted for multi-platform compilation
Fixed URL input could only enter numbers
2025-03-17 22:26:52 +00:00
chinosk ade47131f9
add version consistency check 2025-01-22 16:03:10 +00:00
chinosk 0218543935
fixed `LoginAsIOS` not working in `1.8.0` 2025-01-20 20:13:07 +00:00
chinosk 60c846b2f0
update README 2025-01-09 07:28:00 +00:00
chinosk 01591e61c0
Merge pull request #7 from imas-tools/feature-masterdb-localization
MasterDB Localization
2025-01-09 01:21:08 -06:00
chinosk 56c066bf42
bump version to `v2.0.1` 2025-01-09 06:54:23 +00:00
chinosk 35c2b9f489
MasterDB Localization 2025-01-05 22:36:12 +00:00
chinosk c50fdfd678
bump version to `1.6.8` 2024-12-24 02:50:27 +00:00
chinosk 3c1d1f139a
fix game crash 2024-12-24 02:49:30 +00:00
chinosk 6ac94178fa
add ListEditor tool function 2024-12-24 02:33:56 +00:00
chinosk c27085772f
bump version to `1.6.7` 2024-12-24 00:52:55 +00:00
chinosk 361c48e2c9
fix: incomplete hooks 2024-12-24 00:52:11 +00:00
chinosk e9ba8b58fd
bump version 2024-12-23 00:49:04 +00:00
chinosk e03736bd7d
fix crash when using remote files 2024-12-23 00:44:10 +00:00
chinosk 06b552a097
Built-in patcher supports Android 15
Source: JingMatrix/LSPatch
2024-12-01 04:51:53 +00:00
chinosk bd9bcae01d
Add `login as ios`.
Trim version string.
2024-12-01 03:49:58 +00:00
49 changed files with 3290 additions and 449 deletions

View File

@ -3,21 +3,14 @@
- 学园偶像大师 本地化插件 - 学园偶像大师 本地化插件
- **开发中** - **开发中**
- 下游更改将API版本提升至101并暂时禁用Patcher
# Usage # Usage
- 这是一个 XPosed 插件,已 Root 用户可以使用 [LSPosed](https://github.com/LSPosed/LSPosed),未 Root 用户可以使用 [LSPatch](https://github.com/LSPosed/LSPatch)。 - 这是一个 XPosed 插件,已 Root 用户可以使用 [LSPosed](https://github.com/LSPosed/LSPosed),未 Root 用户可以使用 [LSPatch](https://github.com/LSPosed/LSPatch)。
- 安卓 15 及以上的用户,请使用 [JingMatrix/LSPosed](https://github.com/JingMatrix/LSPosed) 或 [JingMatrix/LSPatch](https://github.com/JingMatrix/LSPatch)。因为原版已停止更新。
# TODO
- [x] 卡片信息、TIPS 等部分的文本 hook (`generic`)
- [ ] 更多类型的文件替换
- [x] LSPatch 集成模式无效
... and more

View File

@ -15,8 +15,9 @@ android {
applicationId "io.github.chinosk.gakumas.localify" applicationId "io.github.chinosk.gakumas.localify"
minSdk 29 minSdk 29
targetSdk 34 targetSdk 34
versionCode 4 versionCode 12
versionName "v1.6.3" versionName "v3.2.0"
buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@ -129,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"

View File

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

View File

@ -42,6 +42,7 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
GakumasLocalify/Log.cpp GakumasLocalify/Log.cpp
GakumasLocalify/Misc.cpp GakumasLocalify/Misc.cpp
GakumasLocalify/Local.cpp GakumasLocalify/Local.cpp
GakumasLocalify/MasterLocal.cpp
GakumasLocalify/camera/baseCamera.cpp GakumasLocalify/camera/baseCamera.cpp
GakumasLocalify/camera/camera.cpp GakumasLocalify/camera/camera.cpp
GakumasLocalify/config/Config.cpp GakumasLocalify/config/Config.cpp
@ -67,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

@ -1,29 +1,58 @@
#define KEY_W 51 #include "../platformDefine.hpp"
#define KEY_S 47
#define KEY_A 29
#define KEY_D 32
#define KEY_R 46
#define KEY_Q 45
#define KEY_E 33
#define KEY_F 34
#define KEY_I 37
#define KEY_K 39
#define KEY_J 38
#define KEY_L 40
#define KEY_V 50
#define KEY_UP 19
#define KEY_DOWN 20
#define KEY_LEFT 21
#define KEY_RIGHT 22
#define KEY_CTRL 113
#define KEY_SHIFT 59
#define KEY_ALT 57
#define KEY_SPACE 62
#define KEY_ADD 70
#define KEY_SUB 69
#define WM_KEYDOWN 0 #ifndef GKMS_WINDOWS
#define WM_KEYUP 1 #define KEY_W 51
#define KEY_S 47
#define KEY_A 29
#define KEY_D 32
#define KEY_R 46
#define KEY_Q 45
#define KEY_E 33
#define KEY_F 34
#define KEY_I 37
#define KEY_K 39
#define KEY_J 38
#define KEY_L 40
#define KEY_V 50
#define KEY_UP 19
#define KEY_DOWN 20
#define KEY_LEFT 21
#define KEY_RIGHT 22
#define KEY_CTRL 113
#define KEY_SHIFT 59
#define KEY_ALT 57
#define KEY_SPACE 62
#define KEY_ADD 70
#define KEY_SUB 69
#define WM_KEYDOWN 0
#define WM_KEYUP 1
#else
#define KEY_W 'W'
#define KEY_S 'S'
#define KEY_A 'A'
#define KEY_D 'D'
#define KEY_R 'R'
#define KEY_Q 'Q'
#define KEY_E 'E'
#define KEY_F 'F'
#define KEY_I 'I'
#define KEY_K 'K'
#define KEY_J 'J'
#define KEY_L 'L'
#define KEY_V 'V'
#define KEY_UP 38
#define KEY_DOWN 40
#define KEY_LEFT 37
#define KEY_RIGHT 39
#define KEY_CTRL 17
#define KEY_SHIFT 16
#define KEY_ALT 18
#define KEY_SPACE 32
#define KEY_ADD 187
#define KEY_SUB 189
#endif
#define BTN_A 96 #define BTN_A 96
#define BTN_B 97 #define BTN_B 97

File diff suppressed because it is too large Load Diff

View File

@ -14,14 +14,96 @@ namespace Il2cppUtils {
const char* namespaze; const char* namespaze;
}; };
struct Il2CppObject
{
union
{
void* klass;
void* vtable;
};
void* monitor;
};
enum Il2CppTypeEnum
{
IL2CPP_TYPE_END = 0x00, /* End of List */
IL2CPP_TYPE_VOID = 0x01,
IL2CPP_TYPE_BOOLEAN = 0x02,
IL2CPP_TYPE_CHAR = 0x03,
IL2CPP_TYPE_I1 = 0x04,
IL2CPP_TYPE_U1 = 0x05,
IL2CPP_TYPE_I2 = 0x06,
IL2CPP_TYPE_U2 = 0x07,
IL2CPP_TYPE_I4 = 0x08,
IL2CPP_TYPE_U4 = 0x09,
IL2CPP_TYPE_I8 = 0x0a,
IL2CPP_TYPE_U8 = 0x0b,
IL2CPP_TYPE_R4 = 0x0c,
IL2CPP_TYPE_R8 = 0x0d,
IL2CPP_TYPE_STRING = 0x0e,
IL2CPP_TYPE_PTR = 0x0f,
IL2CPP_TYPE_BYREF = 0x10,
IL2CPP_TYPE_VALUETYPE = 0x11,
IL2CPP_TYPE_CLASS = 0x12,
IL2CPP_TYPE_VAR = 0x13,
IL2CPP_TYPE_ARRAY = 0x14,
IL2CPP_TYPE_GENERICINST = 0x15,
IL2CPP_TYPE_TYPEDBYREF = 0x16,
IL2CPP_TYPE_I = 0x18,
IL2CPP_TYPE_U = 0x19,
IL2CPP_TYPE_FNPTR = 0x1b,
IL2CPP_TYPE_OBJECT = 0x1c,
IL2CPP_TYPE_SZARRAY = 0x1d,
IL2CPP_TYPE_MVAR = 0x1e,
IL2CPP_TYPE_CMOD_REQD = 0x1f,
IL2CPP_TYPE_CMOD_OPT = 0x20,
IL2CPP_TYPE_INTERNAL = 0x21,
IL2CPP_TYPE_MODIFIER = 0x40,
IL2CPP_TYPE_SENTINEL = 0x41,
IL2CPP_TYPE_PINNED = 0x45,
IL2CPP_TYPE_ENUM = 0x55
};
typedef struct Il2CppType
{
void* dummy;
unsigned int attrs : 16;
Il2CppTypeEnum type : 8;
unsigned int num_mods : 6;
unsigned int byref : 1;
unsigned int pinned : 1;
} Il2CppType;
struct Il2CppReflectionType
{
Il2CppObject object;
const Il2CppType* type;
};
struct Resolution_t {
int width;
int height;
int herz;
};
struct FieldInfo {
const char* name;
const Il2CppType* type;
uintptr_t parent;
int32_t offset;
uint32_t token;
};
struct MethodInfo { struct MethodInfo {
uintptr_t methodPointer; uintptr_t methodPointer;
uintptr_t invoker_method; uintptr_t invoker_method;
const char* name; const char* name;
uintptr_t klass; uintptr_t klass;
//const Il2CppType* return_type; const Il2CppType* return_type;
//const ParameterInfo* parameters; //const ParameterInfo* parameters;
const void* return_type; // const void* return_type;
const void* parameters; const void* parameters;
uintptr_t methodDefinition; uintptr_t methodDefinition;
uintptr_t genericContainer; uintptr_t genericContainer;
@ -36,13 +118,7 @@ namespace Il2cppUtils {
uint8_t is_marshaled_from_native : 1; uint8_t is_marshaled_from_native : 1;
}; };
struct Resolution_t { static UnityResolve::Class* GetClass(const std::string& assemblyName, const std::string& nameSpaceName,
int width;
int height;
int herz;
};
UnityResolve::Class* GetClass(const std::string& assemblyName, const std::string& nameSpaceName,
const std::string& className) { const std::string& className) {
const auto assembly = UnityResolve::Get(assemblyName); const auto assembly = UnityResolve::Get(assemblyName);
if (!assembly) { if (!assembly) {
@ -81,7 +157,7 @@ namespace Il2cppUtils {
return ret; return ret;
}*/ }*/
UnityResolve::Method* GetMethod(const std::string& assemblyName, const std::string& nameSpaceName, static UnityResolve::Method* GetMethod(const std::string& assemblyName, const std::string& nameSpaceName,
const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) { const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
const auto assembly = UnityResolve::Get(assemblyName); const auto assembly = UnityResolve::Get(assemblyName);
if (!assembly) { if (!assembly) {
@ -108,7 +184,7 @@ namespace Il2cppUtils {
return method; return method;
} }
void* GetMethodPointer(const std::string& assemblyName, const std::string& nameSpaceName, static void* GetMethodPointer(const std::string& assemblyName, const std::string& nameSpaceName,
const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) { const std::string& className, const std::string& methodName, const std::vector<std::string>& args = {}) {
auto method = GetMethod(assemblyName, nameSpaceName, className, methodName, args); auto method = GetMethod(assemblyName, nameSpaceName, className, methodName, args);
if (method) { if (method) {
@ -117,20 +193,28 @@ namespace Il2cppUtils {
return nullptr; return nullptr;
} }
void* il2cpp_resolve_icall(const char* s) { static void* il2cpp_resolve_icall(const char* s) {
return UnityResolve::Invoke<void*>("il2cpp_resolve_icall", s); return UnityResolve::Invoke<void*>("il2cpp_resolve_icall", s);
} }
Il2CppClassHead* get_class_from_instance(const void* instance) { static Il2CppClassHead* get_class_from_instance(const void* instance) {
return static_cast<Il2CppClassHead*>(*static_cast<void* const*>(std::assume_aligned<alignof(void*)>(instance))); return static_cast<Il2CppClassHead*>(*static_cast<void* const*>(std::assume_aligned<alignof(void*)>(instance)));
} }
MethodInfo* il2cpp_class_get_method_from_name(void* klass, const char* name, int argsCount) { static MethodInfo* il2cpp_class_get_method_from_name(void* klass, const char* name, int argsCount) {
return UnityResolve::Invoke<MethodInfo*>("il2cpp_class_get_method_from_name", klass, name, argsCount); return UnityResolve::Invoke<MethodInfo*>("il2cpp_class_get_method_from_name", klass, name, argsCount);
} }
void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate) static uintptr_t il2cpp_class_get_method_pointer_from_name(void* klass, const char* name, int argsCount) {
{ auto findKlass = il2cpp_class_get_method_from_name(klass, name, argsCount);
if (findKlass) {
return findKlass->methodPointer;
}
Log::ErrorFmt("method: %s not found", name);
return 0;
}
static void* find_nested_class(void* klass, std::predicate<void*> auto&& predicate) {
void* iter{}; void* iter{};
while (const auto curNestedClass = UnityResolve::Invoke<void*>("il2cpp_class_get_nested_types", klass, &iter)) while (const auto curNestedClass = UnityResolve::Invoke<void*>("il2cpp_class_get_nested_types", klass, &iter))
{ {
@ -143,22 +227,267 @@ namespace Il2cppUtils {
return nullptr; return nullptr;
} }
void* find_nested_class_from_name(void* klass, const char* name) static void* find_nested_class_from_name(void* klass, const char* name) {
{
return find_nested_class(klass, [name = std::string_view(name)](void* nestedClass) { return find_nested_class(klass, [name = std::string_view(name)](void* nestedClass) {
return static_cast<Il2CppClassHead*>(nestedClass)->name == name; return static_cast<Il2CppClassHead*>(nestedClass)->name == name;
}); });
} }
template <typename RType> template <typename RType>
auto ClassGetFieldValue(void* obj, UnityResolve::Field* field) -> RType { static auto ClassGetFieldValue(void* obj, UnityResolve::Field* field) -> RType {
return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset); return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset);
} }
template <typename RType> template <typename RType>
auto ClassSetFieldValue(void* obj, UnityResolve::Field* field, RType value) -> void { static auto ClassGetFieldValue(void* obj, FieldInfo* field) -> RType {
return *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset);
}
template <typename T>
static auto ClassSetFieldValue(void* obj, UnityResolve::Field* field, T value) -> void {
const auto fieldPtr = static_cast<std::byte*>(obj) + field->offset;
std::memcpy(fieldPtr, std::addressof(value), sizeof(T));
}
template <typename RType>
static auto ClassSetFieldValue(void* obj, FieldInfo* field, RType value) -> void {
*reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset) = value; *reinterpret_cast<RType*>(reinterpret_cast<uintptr_t>(obj) + field->offset) = value;
} }
static void* get_system_class_from_reflection_type_str(const char* typeStr, const char* assemblyName = "mscorlib") {
using Il2CppString = UnityResolve::UnityType::String;
static auto assemblyLoad = reinterpret_cast<void* (*)(Il2CppString*)>(
GetMethodPointer("mscorlib.dll", "System.Reflection",
"Assembly", "Load", {"*"})
);
static auto assemblyGetType = reinterpret_cast<Il2CppReflectionType * (*)(void*, Il2CppString*)>(
GetMethodPointer("mscorlib.dll", "System.Reflection",
"Assembly", "GetType", {"*"})
);
static auto reflectionAssembly = assemblyLoad(Il2CppString::New(assemblyName));
auto reflectionType = assemblyGetType(reflectionAssembly, Il2CppString::New(typeStr));
return UnityResolve::Invoke<void*>("il2cpp_class_from_system_type", reflectionType);
}
static std::unordered_map<std::string, std::unordered_map<int, std::string>> enumToValueMapCache{};
static std::unordered_map<int, std::string> EnumToValueMap(Il2CppClassHead* enumClass, bool useCache) {
std::unordered_map<int, std::string> ret{};
auto isEnum = UnityResolve::Invoke<bool>("il2cpp_class_is_enum", enumClass);
if (isEnum) {
Il2cppUtils::FieldInfo* field = nullptr;
void* iter = nullptr;
std::string cacheName = std::string(enumClass->namespaze) + "::" + enumClass->name;
if (useCache) {
if (auto it = enumToValueMapCache.find(cacheName); it != enumToValueMapCache.end()) {
return it->second;
}
}
while ((field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>("il2cpp_class_get_fields", enumClass, &iter))) {
// Log::DebugFmt("field: %s, off: %d", field->name, field->offset);
if (field->offset > 0) continue; // 非 static
if (strcmp(field->name, "value__") == 0) {
continue;
}
int value;
UnityResolve::Invoke<void>("il2cpp_field_static_get_value", field, &value);
// Log::DebugFmt("returnClass: %s - %s: 0x%x", enumClass->name, field->name, value);
std::string itemName = std::string(enumClass->name) + "_" + field->name;
ret.emplace(value, std::move(itemName));
}
if (useCache) {
enumToValueMapCache.emplace(std::move(cacheName), ret);
}
}
return ret;
}
template <typename T = void*>
static void iterate_IEnumerable(const void* obj, std::invocable<T> auto&& receiver)
{
const auto klass = get_class_from_instance(obj);
const auto getEnumeratorMethod = reinterpret_cast<void* (*)(const void*)>(il2cpp_class_get_method_from_name(klass, "GetEnumerator", 0)->methodPointer);
const auto enumerator = getEnumeratorMethod(obj);
const auto enumeratorClass = get_class_from_instance(enumerator);
const auto getCurrentMethod = reinterpret_cast<T(*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "get_Current", 0)->methodPointer);
const auto moveNextMethod = reinterpret_cast<bool(*)(void*)>(il2cpp_class_get_method_from_name(enumeratorClass, "MoveNext", 0)->methodPointer);
while (moveNextMethod(enumerator))
{
static_cast<decltype(receiver)>(receiver)(getCurrentMethod(enumerator));
}
}
namespace Tools {
template <typename T = void*>
class CSListEditor {
public:
CSListEditor(void* list) {
list_klass = get_class_from_instance(list);
lst = list;
lst_get_Count_method = il2cpp_class_get_method_from_name(list_klass, "get_Count", 0);
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_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_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_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) {
lst_Add(lst, value, lst_Add_method);
}
bool Contains(T value) {
return lst_Contains(lst, value, lst_Contains_method);
}
T get_Item(int index) {
return lst_get_Item(lst, index, lst_get_Item_method);
}
void set_Item(int index, T value) {
return lst_set_Item(lst, index, value, lst_set_Item_method);
}
int get_Count() {
return lst_get_Count(lst, lst_get_Count_method);
}
T operator[] (int key) {
return get_Item(key);
}
class Iterator {
public:
Iterator(CSListEditor<T>* editor, int index) : editor(editor), index(index) {}
T operator*() const {
return editor->get_Item(index);
}
Iterator& operator++() {
++index;
return *this;
}
bool operator!=(const Iterator& other) const {
return index != other.index;
}
private:
CSListEditor<T>* editor;
int index;
};
Iterator begin() {
return Iterator(this, 0);
}
Iterator end() {
return Iterator(this, get_Count());
}
void* lst;
void* list_klass;
private:
typedef T(*lst_get_Item_t)(void*, int, void* mtd);
typedef void(*lst_Add_t)(void*, T, void* mtd);
typedef void(*lst_set_Item_t)(void*, int, T, 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_Add_method;
MethodInfo* lst_get_Count_method;
MethodInfo* lst_set_Item_method;
MethodInfo* lst_Contains_method;
lst_get_Item_t lst_get_Item;
lst_set_Item_t lst_set_Item;
lst_Add_t lst_Add;
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);
@ -90,7 +98,7 @@ namespace GakumasLocal::Local {
} }
std::ifstream file(filePath); std::ifstream file(filePath);
if (!file.is_open()) { if (!file.is_open()) {
Log::ErrorFmt("Load %s failed.\n", filePath.c_str()); Log::ErrorFmt("Load %s failed.\n", filePath.string().c_str());
return; return;
} }
std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>()); std::string fileContent((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
@ -112,7 +120,7 @@ namespace GakumasLocal::Local {
} }
} }
catch (std::exception& e) { catch (std::exception& e) {
Log::ErrorFmt("Load %s failed: %s\n", filePath.c_str(), e.what()); Log::ErrorFmt("Load %s failed: %s\n", filePath.string().c_str(), e.what());
} }
} }
@ -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);
@ -447,7 +464,7 @@ namespace GakumasLocal::Local {
const auto targetFilePath = basePath / "local-files" / "resource" / name; const auto targetFilePath = basePath / "local-files" / "resource" / name;
// Log::DebugFmt("GetResourceText: %s", targetFilePath.c_str()); // Log::DebugFmt("GetResourceText: %s", targetFilePath.c_str());
if (exists(targetFilePath)) { if (exists(targetFilePath)) {
auto readStr = readFileToString(targetFilePath); auto readStr = readFileToString(targetFilePath.string());
*ret = readStr; *ret = readStr;
return true; return true;
} }

View File

@ -3,8 +3,11 @@
#include <string> #include <string>
#include <filesystem> #include <filesystem>
#include <unordered_set>
namespace GakumasLocal::Local { namespace GakumasLocal::Local {
extern std::unordered_set<std::string> translatedText;
std::filesystem::path GetBasePath(); std::filesystem::path GetBasePath();
void LoadData(); void LoadData();
bool GetI18n(const std::string& key, std::string* ret); bool GetI18n(const std::string& key, std::string* ret);

View File

@ -1,14 +1,19 @@
#include "Log.h" #include "Log.h"
#include <android/log.h> #include "Misc.hpp"
#include <Misc.hpp>
#include <sstream> #include <sstream>
#include <string> #include <string>
#include <thread> #include <thread>
#include <queue> #include <queue>
#include <cstdarg>
#ifndef GKMS_WINDOWS
#include <android/log.h>
extern JavaVM* g_javaVM;
extern jclass g_gakumasHookMainClass;
extern jmethodID showToastMethodId;
#endif // GKMS_WINDOWS
extern JavaVM* g_javaVM;
extern jclass g_gakumasHookMainClass;
extern jmethodID showToastMethodId;
#define GetParamStringResult(name)\ #define GetParamStringResult(name)\
va_list args;\ va_list args;\
@ -75,6 +80,7 @@ namespace GakumasLocal::Log {
__android_log_write(prio, "GakumasLog", result.c_str()); __android_log_write(prio, "GakumasLog", result.c_str());
} }
/*
void ShowToastJNI(const char* text) { void ShowToastJNI(const char* text) {
DebugFmt("Toast: %s", text); DebugFmt("Toast: %s", text);
@ -99,15 +105,19 @@ namespace GakumasLocal::Log {
g_javaVM->DetachCurrentThread(); g_javaVM->DetachCurrentThread();
}).detach(); }).detach();
} }*/
void ShowToast(const std::string& text) { void ShowToast(const std::string& text) {
#ifndef GKMS_WINDOWS
showingToasts.push(text); showingToasts.push(text);
#else
InfoFmt("Toast: %s", text.c_str());
#endif
} }
void ShowToast(const char* text) { void ShowToast(const char* text) {
DebugFmt("Toast: %s", text); // DebugFmt("Toast: %s", text);
return ShowToast(std::string(text)); return ShowToast(std::string(text));
} }
@ -125,6 +135,7 @@ namespace GakumasLocal::Log {
return ret; return ret;
} }
#ifndef GKMS_WINDOWS
void ToastLoop(JNIEnv *env, jclass clazz) { void ToastLoop(JNIEnv *env, jclass clazz) {
const auto toastString = GetQueuedToast(); const auto toastString = GetQueuedToast();
if (toastString.empty()) return; if (toastString.empty()) return;
@ -140,4 +151,6 @@ namespace GakumasLocal::Log {
_showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V"); _showToastMethodId = env->GetStaticMethodID(clazz, "showToast", "(Ljava/lang/String;)V");
} }
} }
#endif
} }

View File

@ -1,8 +1,14 @@
#ifndef GAKUMAS_LOCALIFY_LOG_H #ifndef GAKUMAS_LOCALIFY_LOG_H
#define GAKUMAS_LOCALIFY_LOG_H #define GAKUMAS_LOCALIFY_LOG_H
#include "../platformDefine.hpp"
#include <string> #include <string>
#include <jni.h>
#ifndef GKMS_WINDOWS
#include <jni.h>
#endif
namespace GakumasLocal::Log { namespace GakumasLocal::Log {
std::string StringFormat(const char* fmt, ...); std::string StringFormat(const char* fmt, ...);
@ -18,7 +24,9 @@ namespace GakumasLocal::Log {
void ShowToast(const char* text); void ShowToast(const char* text);
void ShowToastFmt(const char* fmt, ...); void ShowToastFmt(const char* fmt, ...);
#ifndef GKMS_WINDOWS
void ToastLoop(JNIEnv *env, jclass clazz); void ToastLoop(JNIEnv *env, jclass clazz);
#endif
} }
#endif //GAKUMAS_LOCALIFY_LOG_H #endif //GAKUMAS_LOCALIFY_LOG_H

View File

@ -0,0 +1,809 @@
#include "MasterLocal.h"
#include "Local.h"
#include "Il2cppUtils.hpp"
#include "config/Config.hpp"
#include <filesystem>
#include <fstream>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include <regex>
#include <nlohmann/json.hpp>
namespace GakumasLocal::MasterLocal {
using Il2cppString = UnityResolve::UnityType::String;
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldSetCache;
static std::unordered_map<std::string, Il2cppUtils::MethodInfo*> fieldGetCache;
enum class JsonValueType {
JVT_String,
JVT_Int,
JVT_Object,
JVT_ArrayObject,
JVT_ArrayString,
JVT_Unsupported,
JVT_NeedMore_EmptyArray
};
struct ItemRule {
std::vector<std::string> mainPrimaryKey;
std::map<std::string, std::vector<std::string>> subPrimaryKey;
std::vector<std::string> mainLocalKey;
std::map<std::string, std::vector<std::string>> subLocalKey;
};
struct TableLocalData {
ItemRule itemRule;
std::unordered_map<std::string, JsonValueType> mainKeyType;
std::unordered_map<std::string, std::unordered_map<std::string, JsonValueType>> subKeyType;
std::unordered_map<std::string, std::string> transData;
std::unordered_map<std::string, std::vector<std::string>> transStrListData;
[[nodiscard]] JsonValueType GetMainKeyType(const std::string& mainKey) const {
if (auto it = mainKeyType.find(mainKey); it != mainKeyType.end()) {
return it->second;
}
return JsonValueType::JVT_Unsupported;
}
[[nodiscard]] JsonValueType GetSubKeyType(const std::string& parentKey, const std::string& subKey) const {
if (auto it = subKeyType.find(parentKey); it != subKeyType.end()) {
if (auto subIt = it->second.find(subKey); subIt != it->second.end()) {
return subIt->second;
}
}
return JsonValueType::JVT_Unsupported;
}
};
static std::unordered_map<std::string, TableLocalData> masterLocalData;
class FieldController {
void* self;
std::string self_klass_name;
static std::string capitalizeFirstLetter(const std::string& input) {
if (input.empty()) return input;
std::string result = input;
result[0] = static_cast<char>(std::toupper(result[0]));
return result;
}
Il2cppUtils::MethodInfo* GetGetSetMethodFromCache(const std::string& fieldName, int argsCount,
std::unordered_map<std::string, Il2cppUtils::MethodInfo*>& fromCache, const std::string& prefix = "set_") {
const std::string methodName = prefix + capitalizeFirstLetter(fieldName);
const std::string searchName = self_klass_name + "." + methodName;
if (auto it = fromCache.find(searchName); it != fromCache.end()) {
return it->second;
}
auto set_mtd = Il2cppUtils::il2cpp_class_get_method_from_name(
self_klass,
methodName.c_str(),
argsCount
);
fromCache.emplace(searchName, set_mtd);
return set_mtd;
}
public:
Il2cppUtils::Il2CppClassHead* self_klass;
explicit FieldController(void* from) {
if (!from) {
self = nullptr;
return;
}
self = from;
self_klass = Il2cppUtils::get_class_from_instance(self);
if (self_klass) {
self_klass_name = self_klass->name;
}
}
template<typename T>
T ReadField(const std::string& fieldName) {
if (!self) return T();
auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
if (get_mtd) {
return reinterpret_cast<T (*)(void*, void*)>(get_mtd->methodPointer)(self, get_mtd);
}
auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
"il2cpp_class_get_field_from_name",
self_klass,
(fieldName + '_').c_str()
);
if (!field) {
return T();
}
return Il2cppUtils::ClassGetFieldValue<T>(self, field);
}
template<typename T>
void SetField(const std::string& fieldName, T value) {
if (!self) return;
auto set_mtd = GetGetSetMethodFromCache(fieldName, 1, fieldSetCache, "set_");
if (set_mtd) {
reinterpret_cast<void (*)(void*, T, void*)>(
set_mtd->methodPointer
)(self, value, set_mtd);
return;
}
auto field = UnityResolve::Invoke<Il2cppUtils::FieldInfo*>(
"il2cpp_class_get_field_from_name",
self_klass,
(fieldName + '_').c_str()
);
if (!field) return;
Il2cppUtils::ClassSetFieldValue(self, field, value);
}
int ReadIntField(const std::string& fieldName) {
return ReadField<int>(fieldName);
}
Il2cppString* ReadStringField(const std::string& fieldName) {
if (!self) return nullptr;
auto get_mtd = GetGetSetMethodFromCache(fieldName, 0, fieldGetCache, "get_");
if (!get_mtd) {
return ReadField<Il2cppString*>(fieldName);
}
auto returnClass = UnityResolve::Invoke<Il2cppUtils::Il2CppClassHead*>(
"il2cpp_class_from_type",
UnityResolve::Invoke<void*>("il2cpp_method_get_return_type", get_mtd)
);
if (!returnClass) {
return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
get_mtd->methodPointer
)(self, get_mtd);
}
auto isEnum = UnityResolve::Invoke<bool>("il2cpp_class_is_enum", returnClass);
if (!isEnum) {
return reinterpret_cast<Il2cppString* (*)(void*, void*)>(
get_mtd->methodPointer
)(self, get_mtd);
}
auto enumMap = Il2cppUtils::EnumToValueMap(returnClass, true);
auto enumValue = reinterpret_cast<int (*)(void*, void*)>(
get_mtd->methodPointer
)(self, get_mtd);
if (auto it = enumMap.find(enumValue); it != enumMap.end()) {
return Il2cppString::New(it->second);
}
return nullptr;
}
void SetStringField(const std::string& fieldName, const std::string& value) {
if (!self) return;
auto newString = Il2cppString::New(value);
SetField(fieldName, newString);
}
void SetStringListField(const std::string& fieldName, const std::vector<std::string>& data) {
if (!self) return;
static auto List_String_klass = Il2cppUtils::get_system_class_from_reflection_type_str(
"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 = 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);
for (auto& s : data) {
newListEditor.Add(Il2cppString::New(s));
}
SetField(fieldName, newList);
}
void* ReadObjectField(const std::string& fieldName) {
if (!self) return nullptr;
return ReadField<void*>(fieldName);
}
void* ReadObjectListField(const std::string& fieldName) {
if (!self) return nullptr;
return ReadField<void*>(fieldName);
}
static FieldController CreateSubFieldController(void* subObj) {
return FieldController(subObj);
}
FieldController CreateSubFieldController(const std::string& subObjName) {
auto field = ReadObjectField(subObjName);
return FieldController(field);
}
};
JsonValueType checkJsonValueType(const nlohmann::json& j) {
if (j.is_string()) return JsonValueType::JVT_String;
if (j.is_number_integer()) return JsonValueType::JVT_Int;
if (j.is_object()) return JsonValueType::JVT_Object;
if (j.is_array()) {
if (!j.empty()) {
if (j.begin()->is_object()) {
return JsonValueType::JVT_ArrayObject;
}
else if (j.begin()->is_string()) {
return JsonValueType::JVT_ArrayString;
}
}
else {
return JsonValueType::JVT_NeedMore_EmptyArray;
}
}
return JsonValueType::JVT_Unsupported;
}
std::string ReadFileToString(const std::filesystem::path& path) {
std::ifstream ifs(path, std::ios::binary);
if (!ifs) return {};
std::stringstream buffer;
buffer << ifs.rdbuf();
return buffer.str();
}
namespace Load {
std::vector<std::string> ArrayStrJsonToVec(nlohmann::json& data) {
return data;
}
bool BuildObjectItemLocalRule(nlohmann::json& transData, ItemRule& itemRule) {
// transData: data[]
bool hasSuccess = false;
for (auto& data : transData) {
// data: {"id": "xxx", "produceDescriptions": [{"k", "v"}], "descriptions": {"k2", "v2"}}
if (!data.is_object()) continue;
for (auto& [key, value] : data.items()) {
// key: "id", value: "xxx"
// key: "produceDescriptions", value: [{"k", "v"}]
const auto valueType = checkJsonValueType(value);
switch (valueType) {
case JsonValueType::JVT_String:
// case JsonValueType::JVT_Int:
case JsonValueType::JVT_ArrayString: {
if (std::find(itemRule.mainPrimaryKey.begin(), itemRule.mainPrimaryKey.end(), key) != itemRule.mainPrimaryKey.end()) {
continue;
}
if (auto it = std::find(itemRule.mainLocalKey.begin(), itemRule.mainLocalKey.end(), key); it == itemRule.mainLocalKey.end()) {
itemRule.mainLocalKey.emplace_back(key);
}
hasSuccess = true;
} break;
case JsonValueType::JVT_Object: {
ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
auto vJson = nlohmann::json::array();
vJson.push_back(value);
if (BuildObjectItemLocalRule(vJson, currRule)) {
itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
hasSuccess = true;
}
} break;
case JsonValueType::JVT_ArrayObject: {
for (auto& obj : value) {
// obj: {"k", "v"}
ItemRule currRule{ .mainPrimaryKey = itemRule.subPrimaryKey[key] };
if (BuildObjectItemLocalRule(value, currRule)) {
itemRule.subLocalKey.emplace(key, currRule.mainLocalKey);
hasSuccess = true;
break;
}
}
} break;
case JsonValueType::JVT_Unsupported:
default:
break;
}
}
if (hasSuccess) break;
}
return hasSuccess;
}
bool GetItemRule(nlohmann::json& fullData, ItemRule& itemRule) {
auto& primaryKeys = fullData["rules"]["primaryKeys"];
auto& transData = fullData["data"];
if (!primaryKeys.is_array()) return false;
if (!transData.is_array()) return false;
// 首先构造 mainPrimaryKey 规则
for (auto& pkItem : primaryKeys) {
if (!pkItem.is_string()) {
return false;
}
std::string pk = pkItem;
auto dotCount = std::ranges::count(pk, '.');
if (dotCount == 0) {
itemRule.mainPrimaryKey.emplace_back(pk);
}
else if (dotCount == 1) {
auto [parentKey, subKey] = Misc::StringFormat::split_once(pk, ".");
if (itemRule.subPrimaryKey.contains(parentKey)) {
itemRule.subPrimaryKey[parentKey].emplace_back(subKey);
}
else {
itemRule.subPrimaryKey.emplace(parentKey, std::vector<std::string>{subKey});
}
}
else {
Log::ErrorFmt("Unsupported depth: %d", dotCount);
continue;
}
}
return BuildObjectItemLocalRule(transData, itemRule);
}
std::string BuildBaseMainUniqueKey(nlohmann::json& data, TableLocalData& tableLocalData) {
try {
std::string mainBaseUniqueKey;
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
if (!data.contains(mainPrimaryKey)) {
return "";
}
auto& value = data[mainPrimaryKey];
if (value.is_number_integer()) {
mainBaseUniqueKey.append(std::to_string(value.get<int>()));
}
else {
mainBaseUniqueKey.append(value);
}
mainBaseUniqueKey.push_back('|');
}
return mainBaseUniqueKey;
}
catch (std::exception& e) {
Log::ErrorFmt("LoadData - BuildBaseMainUniqueKey failed: %s", e.what());
throw e;
}
}
void BuildBaseObjectSubUniqueKey(nlohmann::json& value, JsonValueType valueType, std::string& currLocalKey) {
switch (valueType) {
case JsonValueType::JVT_String:
currLocalKey.append(value.get<std::string>()); // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
currLocalKey.push_back('|');
break;
case JsonValueType::JVT_Int:
currLocalKey.append(std::to_string(value.get<int>()));
currLocalKey.push_back('|');
break;
default:
break;
}
}
bool BuildUniqueKeyValue(nlohmann::json& data, TableLocalData& tableLocalData) {
// 首先处理 main 部分
const std::string mainBaseUniqueKey = BuildBaseMainUniqueKey(data, tableLocalData); // p_card-00-acc-0_002|0|
if (mainBaseUniqueKey.empty()) return false;
for (auto& mainLocalKey : tableLocalData.itemRule.mainLocalKey) {
if (!data.contains(mainLocalKey)) continue;
auto& currLocalValue = data[mainLocalKey];
auto currUniqueKey = mainBaseUniqueKey + mainLocalKey; // p_card-00-acc-0_002|0|name
if (tableLocalData.GetMainKeyType(mainLocalKey) == JsonValueType::JVT_ArrayString) {
tableLocalData.transStrListData.emplace(currUniqueKey, ArrayStrJsonToVec(currLocalValue));
}
else {
tableLocalData.transData.emplace(currUniqueKey, currLocalValue);
}
}
// 然后处理 sub 部分
/*
for (const auto& [subPrimaryParentKey, subPrimarySubKeys] : tableLocalData.itemRule.subPrimaryKey) {
if (!data.contains(subPrimaryParentKey)) continue;
const std::string subBaseUniqueKey = mainBaseUniqueKey + subPrimaryParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
auto subValueType = checkJsonValueType(data[subPrimaryParentKey]);
std::string currLocalKey = subBaseUniqueKey; // p_card-00-acc-0_002|0|produceDescriptions|
switch (subValueType) {
case JsonValueType::JVT_Object: {
for (auto& subPrimarySubKey : subPrimarySubKeys) {
if (!data[subPrimaryParentKey].contains(subPrimarySubKey)) continue;
auto& value = data[subPrimaryParentKey][subPrimarySubKey];
auto valueType = tableLocalData.GetSubKeyType(subPrimaryParentKey, subPrimarySubKey);
BuildBaseObjectSubUniqueKey(value, valueType, currLocalKey); // p_card-00-acc-0_002|0|produceDescriptions|ProduceDescriptionType_Exam|
}
} break;
case JsonValueType::JVT_ArrayObject: {
int currIndex = 0;
for (auto& obj : data[subPrimaryParentKey]) {
for (auto& subPrimarySubKey : subPrimarySubKeys) {
}
currIndex++;
}
} break;
default:
break;
}
}*/
for (const auto& [subLocalParentKey, subLocalSubKeys] : tableLocalData.itemRule.subLocalKey) {
if (!data.contains(subLocalParentKey)) continue;
const std::string subBaseUniqueKey = mainBaseUniqueKey + subLocalParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
auto subValueType = checkJsonValueType(data[subLocalParentKey]);
if (subValueType != JsonValueType::JVT_NeedMore_EmptyArray) {
tableLocalData.mainKeyType.emplace(subLocalParentKey, subValueType); // 在这里插入 subParent 的类型
}
switch (subValueType) {
case JsonValueType::JVT_Object: {
for (auto& localSubKey : subLocalSubKeys) {
const std::string currLocalUniqueKey = subBaseUniqueKey + localSubKey; // p_card-00-acc-0_002|0|produceDescriptions|text
if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(data[subLocalParentKey][localSubKey]));
}
else {
tableLocalData.transData.emplace(currLocalUniqueKey, data[subLocalParentKey][localSubKey]);
}
}
} break;
case JsonValueType::JVT_ArrayObject: {
int currIndex = 0;
for (auto& obj : data[subLocalParentKey]) {
for (auto& localSubKey : subLocalSubKeys) {
std::string currLocalUniqueKey = subBaseUniqueKey; // p_card-00-acc-0_002|0|produceDescriptions|
currLocalUniqueKey.push_back('[');
currLocalUniqueKey.append(std::to_string(currIndex));
currLocalUniqueKey.append("]|");
currLocalUniqueKey.append(localSubKey); // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
if (tableLocalData.GetSubKeyType(subLocalParentKey, localSubKey) == JsonValueType::JVT_ArrayString) {
// if (obj[localSubKey].is_array()) {
tableLocalData.transStrListData.emplace(currLocalUniqueKey, ArrayStrJsonToVec(obj[localSubKey]));
}
else if (obj[localSubKey].is_string()) {
tableLocalData.transData.emplace(currLocalUniqueKey, obj[localSubKey]);
}
}
currIndex++;
}
} break;
default:
break;
}
}
return true;
}
#define MainKeyTypeProcess() if (!data.contains(mainPrimaryKey)) { Log::ErrorFmt("mainPrimaryKey: %s not found", mainPrimaryKey.c_str()); isFailed = true; break; } \
auto currType = checkJsonValueType(data[mainPrimaryKey]); \
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
tableLocalData.mainKeyType[mainPrimaryKey] = currType
#define SubKeyTypeProcess() if (!data.contains(subKeyParent)) { Log::ErrorFmt("subKeyParent: %s not found", subKeyParent.c_str()); isFailed = true; break; } \
for (auto& subKey : subKeys) { \
auto& subKeyValue = data[subKeyParent]; \
if (subKeyValue.is_object()) { \
if (!subKeyValue.contains(subKey)) { \
Log::ErrorFmt("subKey: %s not in subKeyParent: %s", subKey.c_str(), subKeyParent.c_str()); isFailed = true; break; \
} \
auto currType = checkJsonValueType(subKeyValue[subKey]); \
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
} \
else if (subKeyValue.is_array()) { \
if (subKeyValue.empty()) goto NextLoop; \
for (auto& i : subKeyValue) { \
if (!i.is_object()) continue; \
if (!i.contains(subKey)) continue; \
auto currType = checkJsonValueType(i[subKey]); \
if (currType == JsonValueType::JVT_NeedMore_EmptyArray) goto NextLoop; \
tableLocalData.subKeyType[subKeyParent].emplace(subKey, currType); \
break; \
} \
} \
else { \
goto NextLoop;\
} \
}
bool GetTableLocalData(nlohmann::json& fullData, TableLocalData& tableLocalData) {
bool isFailed = false;
// 首先 Build mainKeyType 和 subKeyType
for (auto& data : fullData["data"]) {
if (!data.is_object()) continue;
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainPrimaryKey) {
MainKeyTypeProcess();
}
for (auto& mainPrimaryKey : tableLocalData.itemRule.mainLocalKey) {
MainKeyTypeProcess();
}
for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subPrimaryKey) {
SubKeyTypeProcess()
if (isFailed) break;
}
for (const auto& [subKeyParent, subKeys] : tableLocalData.itemRule.subLocalKey) {
SubKeyTypeProcess()
if (isFailed) break;
}
if (!isFailed) break;
NextLoop:
;
}
if (isFailed) return false;
bool hasSuccess = false;
// 然后构造 transData
for (auto& data : fullData["data"]) {
if (!data.is_object()) continue;
if (BuildUniqueKeyValue(data, tableLocalData)) {
hasSuccess = true;
}
}
if (!hasSuccess) {
Log::ErrorFmt("BuildUniqueKeyValue failed.");
}
return hasSuccess;
}
void LoadData() {
masterLocalData.clear();
static auto masterDir = Local::GetBasePath() / "local-files" / "masterTrans";
if (!std::filesystem::is_directory(masterDir)) {
Log::ErrorFmt("LoadData: not found: %s", masterDir.string().c_str());
return;
}
bool isFirstIteration = true;
for (auto& p : std::filesystem::directory_iterator(masterDir)) {
if (isFirstIteration) {
auto totalFileCount = std::distance(
std::filesystem::directory_iterator(masterDir),
std::filesystem::directory_iterator{}
);
UnityResolveProgress::classProgress.total = totalFileCount <= 0 ? 1 : totalFileCount;
isFirstIteration = false;
}
UnityResolveProgress::classProgress.current++;
if (!p.is_regular_file()) continue;
const auto& path = p.path();
if (path.extension() != ".json") continue;
std::string tableName = path.stem().string();
auto fileContent = ReadFileToString(path);
if (fileContent.empty()) continue;
try {
auto j = nlohmann::json::parse(fileContent);
if (!j.contains("rules") || !j["rules"].contains("primaryKeys")) {
continue;
}
ItemRule currRule;
if (!GetItemRule(j, currRule)) {
Log::ErrorFmt("GetItemRule failed: %s", path.string().c_str());
continue;
}
/*
if (tableName == "ProduceStepEventDetail") {
for (auto& i : currRule.mainLocalKey) {
Log::DebugFmt("currRule.mainLocalKey: %s", i.c_str());
}
for (auto& i : currRule.mainPrimaryKey) {
Log::DebugFmt("currRule.mainPrimaryKey: %s", i.c_str());
}
for (auto& i : currRule.subLocalKey) {
for (auto& m : i.second) {
Log::DebugFmt("currRule.subLocalKey: %s - %s", i.first.c_str(), m.c_str());
}
}
for (auto& i : currRule.subPrimaryKey) {
for (auto& m : i.second) {
Log::DebugFmt("currRule.subPrimaryKey: %s - %s", i.first.c_str(), m.c_str());
}
}
}*/
TableLocalData tableLocalData{ .itemRule = currRule };
if (GetTableLocalData(j, tableLocalData)) {
for (auto& i : tableLocalData.transData) {
// Log::DebugFmt("%s: %s -> %s", tableName.c_str(), i.first.c_str(), i.second.c_str());
Local::translatedText.emplace(i.second);
}
for (auto& i : tableLocalData.transStrListData) {
for (auto& str : i.second) {
// Log::DebugFmt("%s[]: %s -> %s", tableName.c_str(), i.first.c_str(), str.c_str());
Local::translatedText.emplace(str);
}
}
/*
if (tableName == "ProduceStepEventDetail") {
for (auto& i : tableLocalData.mainKeyType) {
Log::DebugFmt("mainKeyType: %s -> %d", i.first.c_str(), i.second);
}
for (auto& i : tableLocalData.subKeyType) {
for (auto& m : i.second) {
Log::DebugFmt("subKeyType: %s - %s -> %d", i.first.c_str(), m.first.c_str(), m.second);
}
}
}*/
// JVT_ArrayString in HelpCategory, ProduceStory, Tutorial
masterLocalData.emplace(tableName, std::move(tableLocalData));
}
else {
Log::ErrorFmt("GetTableLocalData failed: %s", path.string().c_str());
}
} catch (std::exception& e) {
Log::ErrorFmt("MasterLocal::LoadData: parse error in '%s': %s",
path.string().c_str(), e.what());
}
}
}
}
void LoadData() {
return Load::LoadData();
}
std::string GetTransString(const std::string& key, const TableLocalData& localData) {
if (auto it = localData.transData.find(key); it != localData.transData.end()) {
return it->second;
}
return {};
}
std::vector<std::string> GetTransArrayString(const std::string& key, const TableLocalData& localData) {
if (auto it = localData.transStrListData.find(key); it != localData.transStrListData.end()) {
return it->second;
}
return {};
}
void LocalizeMasterItem(FieldController& fc, const std::string& tableName) {
auto it = masterLocalData.find(tableName);
if (it == masterLocalData.end()) return;
const auto& localData = it->second;
// 首先拼 BasePrimaryKey
std::string baseDataKey; // p_card-00-acc-0_002|0|
for (auto& mainPk : localData.itemRule.mainPrimaryKey) {
auto mainPkType = localData.GetMainKeyType(mainPk);
switch (mainPkType) {
case JsonValueType::JVT_Int: {
auto readValue = std::to_string(fc.ReadIntField(mainPk));
baseDataKey.append(readValue);
baseDataKey.push_back('|');
} break;
case JsonValueType::JVT_String: {
auto readValue = fc.ReadStringField(mainPk);
if (!readValue) return;
baseDataKey.append(readValue->ToString());
baseDataKey.push_back('|');
} break;
default:
break;
}
}
// 然后本地化 mainLocal
for (auto& mainLocal : localData.itemRule.mainLocalKey) {
std::string currSearchKey = baseDataKey;
currSearchKey.append(mainLocal); // p_card-00-acc-0_002|0|name
auto localVType = localData.GetMainKeyType(mainLocal);
switch (localVType) {
case JsonValueType::JVT_String: {
auto localValue = GetTransString(currSearchKey, localData);
if (!localValue.empty()) {
fc.SetStringField(mainLocal, localValue);
}
} break;
case JsonValueType::JVT_ArrayString: {
auto localValue = GetTransArrayString(currSearchKey, localData);
if (!localValue.empty()) {
fc.SetStringListField(mainLocal, localValue);
}
} break;
default:
break;
}
}
// 处理 sub
for (const auto& [subParentKey, subLocalKeys] : localData.itemRule.subLocalKey) {
const auto subBaseSearchKey = baseDataKey + subParentKey + '|'; // p_card-00-acc-0_002|0|produceDescriptions|
const auto subParentType = localData.GetMainKeyType(subParentKey);
switch (subParentType) {
case JsonValueType::JVT_Object: {
auto subParentField = fc.CreateSubFieldController(subParentKey);
for (const auto& subLocalKey : subLocalKeys) {
const auto currSearchKey = subBaseSearchKey + subLocalKey; // p_card-00-acc-0_002|0|produceDescriptions|text
auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
if (localKeyType == JsonValueType::JVT_String) {
auto setData = GetTransString(currSearchKey, localData);
if (!setData.empty()) {
subParentField.SetStringField(subLocalKey, setData);
}
}
else if (localKeyType == JsonValueType::JVT_ArrayString) {
auto setData = GetTransArrayString(currSearchKey, localData);
if (!setData.empty()) {
subParentField.SetStringListField(subLocalKey, setData);
}
}
}
} break;
case JsonValueType::JVT_ArrayObject: {
auto subArrField = fc.ReadObjectListField(subParentKey);
if (!subArrField) continue;
Il2cppUtils::Tools::CSListEditor<void*> subListEdit(subArrField);
auto count = subListEdit.get_Count();
for (int idx = 0; idx < count; idx++) {
auto currItem = subListEdit.get_Item(idx);
if (!currItem) continue;
auto currFc = FieldController::CreateSubFieldController(currItem);
std::string currSearchBaseKey = subBaseSearchKey; // p_card-00-acc-0_002|0|produceDescriptions|
currSearchBaseKey.push_back('[');
currSearchBaseKey.append(std::to_string(idx));
currSearchBaseKey.append("]|"); // p_card-00-acc-0_002|0|produceDescriptions|[0]|
for (const auto& subLocalKey : subLocalKeys) {
std::string currSearchKey = currSearchBaseKey + subLocalKey; // p_card-00-acc-0_002|0|produceDescriptions|[0]|text
auto localKeyType = localData.GetSubKeyType(subParentKey, subLocalKey);
/*
if (tableName == "ProduceStepEventDetail") {
Log::DebugFmt("localKeyType: %d currSearchKey: %s", localKeyType, currSearchKey.c_str());
}*/
if (localKeyType == JsonValueType::JVT_String) {
auto setData = GetTransString(currSearchKey, localData);
if (!setData.empty()) {
currFc.SetStringField(subLocalKey, setData);
}
}
else if (localKeyType == JsonValueType::JVT_ArrayString) {
auto setData = GetTransArrayString(currSearchKey, localData);
if (!setData.empty()) {
currFc.SetStringListField(subLocalKey, setData);
}
}
}
}
} break;
default:
break;
}
}
}
void LocalizeMasterItem(void* item, const std::string& tableName) {
if (!Config::useMasterTrans) return;
// Log::DebugFmt("LocalizeMasterItem: %s", tableName.c_str());
FieldController fc(item);
LocalizeMasterItem(fc, tableName);
}
} // namespace GakumasLocal::MasterLocal

View File

@ -0,0 +1,12 @@
#ifndef GAKUMAS_LOCALIFY_MASTERLOCAL_H
#define GAKUMAS_LOCALIFY_MASTERLOCAL_H
#include <string>
namespace GakumasLocal::MasterLocal {
void LoadData();
void LocalizeMasterItem(void* item, const std::string& tableName);
}
#endif //GAKUMAS_LOCALIFY_MASTERLOCAL_H

View File

@ -1,25 +1,172 @@
#include "Misc.hpp" #include "Misc.hpp"
#include <codecvt>
#include <locale>
#include <jni.h>
#include "fmt/core.h" #include "fmt/core.h"
#ifndef GKMS_WINDOWS
#include <jni.h>
#if defined(__ANDROID__) && __ANDROID_API__ >= 31
#include <unicode/ustring.h>
#endif
extern JavaVM* g_javaVM; extern JavaVM* g_javaVM;
#else
#include "cpprest/details/http_helpers.h"
#endif
namespace GakumasLocal::Misc { namespace GakumasLocal::Misc {
#ifdef GKMS_WINDOWS
std::string ToUTF8(const std::wstring_view& str) {
return utility::conversions::to_utf8string(str.data());
}
std::u16string ToUTF16(const std::string_view& str) { std::u16string ToUTF16(const std::string_view& str) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv; std::string input(str);
return utf16conv.from_bytes(str.data(), str.data() + str.size()); 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::string ToUTF8(const std::u16string_view& str) {
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> utf16conv; std::u16string u16(str);
return utf16conv.to_bytes(str.data(), str.data() + str.size()); 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
#ifndef GKMS_WINDOWS
JNIEnv* GetJNIEnv() { JNIEnv* GetJNIEnv() {
if (!g_javaVM) return nullptr; if (!g_javaVM) return nullptr;
JNIEnv* env = nullptr; JNIEnv* env = nullptr;
@ -31,6 +178,7 @@ namespace GakumasLocal::Misc {
} }
return env; return env;
} }
#endif
CSEnum::CSEnum(const std::string& name, const int value) { CSEnum::CSEnum(const std::string& name, const int value) {
this->Add(name, value); this->Add(name, value);
@ -168,6 +316,33 @@ namespace GakumasLocal::Misc {
return fmt; return fmt;
} }
} }
std::vector<std::string> split(const std::string& str, char delimiter) {
std::vector<std::string> result;
std::string current;
for (char c : str) {
if (c == delimiter) {
if (!current.empty()) {
result.push_back(current);
}
current.clear();
} else {
current += c;
}
}
if (!current.empty()) {
result.push_back(current);
}
return result;
}
std::pair<std::string, std::string> split_once(const std::string& str, const std::string& delimiter) {
size_t pos = str.find(delimiter);
if (pos != std::string::npos) {
return {str.substr(0, pos), str.substr(pos + delimiter.size())};
}
return {str, ""};
}
} }
} }

View File

@ -2,11 +2,16 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <jni.h>
#include <deque> #include <deque>
#include <numeric> #include <numeric>
#include <vector> #include <vector>
#include "../platformDefine.hpp"
#ifndef GKMS_WINDOWS
#include <jni.h>
#endif
namespace GakumasLocal { namespace GakumasLocal {
using OpaqueFunctionPointer = void (*)(); using OpaqueFunctionPointer = void (*)();
@ -14,7 +19,13 @@ namespace GakumasLocal {
namespace Misc { namespace Misc {
std::u16string ToUTF16(const std::string_view& str); std::u16string ToUTF16(const std::string_view& str);
std::string ToUTF8(const std::u16string_view& str); std::string ToUTF8(const std::u16string_view& str);
#ifdef GKMS_WINDOWS
std::string ToUTF8(const std::wstring_view& str);
#endif
#ifndef GKMS_WINDOWS
JNIEnv* GetJNIEnv(); JNIEnv* GetJNIEnv();
#endif
class CSEnum { class CSEnum {
public: public:
@ -76,6 +87,8 @@ namespace GakumasLocal {
namespace StringFormat { namespace StringFormat {
std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec); std::string stringFormatString(const std::string& fmt, const std::vector<std::string>& vec);
std::vector<std::string> split(const std::string& str, char delimiter);
std::pair<std::string, std::string> split_once(const std::string& str, const std::string& delimiter);
} }
} }
} }

View File

@ -4,7 +4,13 @@
#include "Misc.hpp" #include "Misc.hpp"
#include <string> #include <string>
#include <memory> #include <memory>
#include <jni.h>
#include "../platformDefine.hpp"
#ifndef GKMS_WINDOWS
#include <jni.h>
#endif // !GKMS_WINDOWS
namespace GakumasLocal { namespace GakumasLocal {
struct HookInstaller struct HookInstaller

View File

@ -1,6 +1,12 @@
#include "baseCamera.hpp" #include "baseCamera.hpp"
#include <thread> #include <thread>
#include "../../platformDefine.hpp"
#ifdef GKMS_WINDOWS
#include <corecrt_math_defines.h>
#endif // GKMS_WINDOWS
namespace BaseCamera { namespace BaseCamera {
using Vector3_t = UnityResolve::UnityType::Vector3; using Vector3_t = UnityResolve::UnityType::Vector3;

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "../deps/UnityResolve/UnityResolve.hpp" #include "../../deps/UnityResolve/UnityResolve.hpp"
enum LonMoveHState { enum LonMoveHState {
LonMoveLeftAndRight, LonMoveLeftAndRight,

View File

@ -1,8 +1,14 @@
#include "baseCamera.hpp" #include "baseCamera.hpp"
#include "camera.hpp" #include "camera.hpp"
#include <thread> #include <thread>
#include "Misc.hpp" #include "../Misc.hpp"
#include "../BaseDefine.h" #include "../BaseDefine.h"
#include "../../platformDefine.hpp"
#ifdef GKMS_WINDOWS
#include <corecrt_math_defines.h>
#endif // GKMS_WINDOWS
namespace GKCamera { namespace GKCamera {

View File

@ -1,6 +1,6 @@
#pragma once #pragma once
#include "baseCamera.hpp" #include "baseCamera.hpp"
#include "Joystick/JoystickEvent.h" #include "../../deps/Joystick/JoystickEvent.h"
namespace GKCamera { namespace GKCamera {
enum class CameraMode { enum class CameraMode {

View File

@ -1,6 +1,8 @@
#include <string> #include <string>
#include "nlohmann/json.hpp" #include "nlohmann/json.hpp"
#include "../Log.h" #include "../Log.h"
#include <thread>
#include <fstream>
namespace GakumasLocal::Config { namespace GakumasLocal::Config {
bool isConfigInit = false; bool isConfigInit = false;
@ -11,16 +13,20 @@ namespace GakumasLocal::Config {
bool replaceFont = true; bool replaceFont = true;
bool forceExportResource = true; bool forceExportResource = true;
bool textTest = false; bool textTest = false;
bool useMasterTrans = true;
int gameOrientation = 0; int gameOrientation = 0;
bool dumpText = false; bool dumpText = false;
bool enableFreeCamera = false; bool enableFreeCamera = false;
int targetFrameRate = 0; int targetFrameRate = 0;
bool unlockAllLive = false; bool unlockAllLive = false;
bool unlockAllLiveCostume = false;
bool enableLiveCustomeDress = false; bool enableLiveCustomeDress = false;
std::string liveCustomeHeadId = ""; std::string liveCustomeHeadId = "";
std::string liveCustomeCostumeId = ""; std::string liveCustomeCostumeId = "";
bool loginAsIOS = false;
bool useCustomeGraphicSettings = false; bool useCustomeGraphicSettings = false;
float renderScale = 0.77f; float renderScale = 0.77f;
int qualitySettingsLevel = 3; int qualitySettingsLevel = 3;
@ -48,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);
@ -61,13 +69,16 @@ namespace GakumasLocal::Config {
GetConfigItem(forceExportResource); GetConfigItem(forceExportResource);
GetConfigItem(gameOrientation); GetConfigItem(gameOrientation);
GetConfigItem(textTest); GetConfigItem(textTest);
GetConfigItem(useMasterTrans);
GetConfigItem(dumpText); GetConfigItem(dumpText);
GetConfigItem(targetFrameRate); GetConfigItem(targetFrameRate);
GetConfigItem(enableFreeCamera); GetConfigItem(enableFreeCamera);
GetConfigItem(unlockAllLive); GetConfigItem(unlockAllLive);
GetConfigItem(unlockAllLiveCostume);
GetConfigItem(enableLiveCustomeDress); GetConfigItem(enableLiveCustomeDress);
GetConfigItem(liveCustomeHeadId); GetConfigItem(liveCustomeHeadId);
GetConfigItem(liveCustomeCostumeId); GetConfigItem(liveCustomeCostumeId);
GetConfigItem(loginAsIOS);
GetConfigItem(useCustomeGraphicSettings); GetConfigItem(useCustomeGraphicSettings);
GetConfigItem(renderScale); GetConfigItem(renderScale);
GetConfigItem(qualitySettingsLevel); GetConfigItem(qualitySettingsLevel);
@ -93,11 +104,74 @@ 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());
} }
isConfigInit = true; isConfigInit = true;
} }
void SaveConfig(const std::string& configPath) {
try {
nlohmann::json config;
#define SetConfigItem(name) config[#name] = name
SetConfigItem(dbgMode);
SetConfigItem(enabled);
SetConfigItem(lazyInit);
SetConfigItem(replaceFont);
SetConfigItem(forceExportResource);
SetConfigItem(gameOrientation);
SetConfigItem(textTest);
SetConfigItem(useMasterTrans);
SetConfigItem(dumpText);
SetConfigItem(targetFrameRate);
SetConfigItem(enableFreeCamera);
SetConfigItem(unlockAllLive);
SetConfigItem(unlockAllLiveCostume);
SetConfigItem(enableLiveCustomeDress);
SetConfigItem(liveCustomeHeadId);
SetConfigItem(liveCustomeCostumeId);
SetConfigItem(loginAsIOS);
SetConfigItem(useCustomeGraphicSettings);
SetConfigItem(renderScale);
SetConfigItem(qualitySettingsLevel);
SetConfigItem(volumeIndex);
SetConfigItem(maxBufferPixel);
SetConfigItem(reflectionQualityLevel);
SetConfigItem(lodQualityLevel);
SetConfigItem(enableBreastParam);
SetConfigItem(bDamping);
SetConfigItem(bStiffness);
SetConfigItem(bSpring);
SetConfigItem(bPendulum);
SetConfigItem(bPendulumRange);
SetConfigItem(bAverage);
SetConfigItem(bRootWeight);
SetConfigItem(bUseArmCorrection);
SetConfigItem(bUseScale);
SetConfigItem(bScale);
SetConfigItem(bUseLimit);
SetConfigItem(bLimitXx);
SetConfigItem(bLimitXy);
SetConfigItem(bLimitYx);
SetConfigItem(bLimitYy);
SetConfigItem(bLimitZx);
SetConfigItem(bLimitZy);
SetConfigItem(dmmUnlockSize);
std::ofstream out(configPath);
if (!out) {
Log::ErrorFmt("SaveConfig error: Cannot open file: %s", configPath.c_str());
return;
}
out << config.dump(4);
Log::Info("SaveConfig success");
}
catch (std::exception& e) {
Log::ErrorFmt("SaveConfig error: %s", e.what());
}
}
} }

View File

@ -10,15 +10,19 @@ namespace GakumasLocal::Config {
extern bool forceExportResource; extern bool forceExportResource;
extern int gameOrientation; extern int gameOrientation;
extern bool textTest; extern bool textTest;
extern bool useMasterTrans;
extern bool dumpText; extern bool dumpText;
extern bool enableFreeCamera; extern bool enableFreeCamera;
extern int targetFrameRate; extern int targetFrameRate;
extern bool unlockAllLive; extern bool unlockAllLive;
extern bool unlockAllLiveCostume;
extern bool enableLiveCustomeDress; extern bool enableLiveCustomeDress;
extern std::string liveCustomeHeadId; extern std::string liveCustomeHeadId;
extern std::string liveCustomeCostumeId; extern std::string liveCustomeCostumeId;
extern bool loginAsIOS;
extern bool useCustomeGraphicSettings; extern bool useCustomeGraphicSettings;
extern float renderScale; extern float renderScale;
extern int qualitySettingsLevel; extern int qualitySettingsLevel;
@ -47,5 +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);
} }

View File

@ -309,7 +309,7 @@ public:
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; // if (!lazyInit) UnityResolveProgress::startInit = false;
} }
else { else {
pDomain = Invoke<void*>("mono_get_root_domain"); pDomain = Invoke<void*>("mono_get_root_domain");
@ -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

@ -0,0 +1,21 @@
#include "shadowhook.h"
#include <android/log.h>
#define ADD_HOOK(name, addr) \
name##_Addr = reinterpret_cast<name##_Type>(addr); \
if (addr) { \
auto stub = hookInstaller->InstallHook(reinterpret_cast<void*>(addr), \
reinterpret_cast<void*>(name##_Hook), \
reinterpret_cast<void**>(&name##_Orig)); \
if (stub == NULL) { \
int error_num = shadowhook_get_errno(); \
const char *error_msg = shadowhook_to_errmsg(error_num); \
Log::ErrorFmt("ADD_HOOK: %s at %p failed: %s", #name, addr, error_msg); \
} \
else { \
hookedStubs.emplace(stub); \
GakumasLocal::Log::InfoFmt("ADD_HOOK: %s at %p", #name, addr); \
} \
} \
else GakumasLocal::Log::ErrorFmt("Hook failed: %s is NULL", #name, addr); \
if (Config::lazyInit) UnityResolveProgress::classProgress.current++

View File

@ -107,6 +107,7 @@ fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
getProgramConfigContent(listOf("transRemoteZipUrl", "useAPIAssetsURL", getProgramConfigContent(listOf("transRemoteZipUrl", "useAPIAssetsURL",
"localAPIAssetsVersion", "p"), programConfig) "localAPIAssetsVersion", "p"), programConfig)
) )
putExtra("lVerName", version)
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
} }
@ -128,7 +129,15 @@ fun <T> T.onClickStartGame() where T : Activity, T : IHasConfigItems {
"io.github.chinosk.gakumas.localify.fileprovider", "io.github.chinosk.gakumas.localify.fileprovider",
File(targetFile.absolutePath) File(targetFile.absolutePath)
) )
intent.setDataAndType(dirUri, "resource/file") // intent.setDataAndType(dirUri, "resource/file")
grantUriPermission(
"com.bandainamcoent.idolmaster_gakuen",
dirUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
intent.putExtra("resource_file", dirUri)
// intent.clipData = ClipData.newRawUri("resource_file", dirUri)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
} }

View File

@ -17,12 +17,15 @@ import kotlinx.coroutines.runBlocking
interface ConfigListener { interface ConfigListener {
fun onEnabledChanged(value: Boolean) fun onEnabledChanged(value: Boolean)
fun onForceExportResourceChanged(value: Boolean) fun onForceExportResourceChanged(value: Boolean)
fun onLoginAsIOSChanged(value: Boolean)
fun onTextTestChanged(value: Boolean) fun onTextTestChanged(value: Boolean)
fun onUseMasterTransChanged(value: Boolean)
fun onReplaceFontChanged(value: Boolean) fun onReplaceFontChanged(value: Boolean)
fun onLazyInitChanged(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)
fun onUnlockAllLiveCostumeChanged(value: Boolean)
fun onLiveCustomeDressChanged(value: Boolean) fun onLiveCustomeDressChanged(value: Boolean)
fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int)
fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int)
@ -115,6 +118,11 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
pushKeyEvent(KeyEvent(1145, 30)) pushKeyEvent(KeyEvent(1145, 30))
} }
override fun onLoginAsIOSChanged(value: Boolean) {
config.loginAsIOS = value
saveConfig()
}
override fun onReplaceFontChanged(value: Boolean) { override fun onReplaceFontChanged(value: Boolean) {
config.replaceFont = value config.replaceFont = value
saveConfig() saveConfig()
@ -131,6 +139,11 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
saveConfig() saveConfig()
} }
override fun onUseMasterTransChanged(value: Boolean) {
config.useMasterTrans = value
saveConfig()
}
override fun onDumpTextChanged(value: Boolean) { override fun onDumpTextChanged(value: Boolean) {
config.dumpText = value config.dumpText = value
saveConfig() saveConfig()
@ -146,6 +159,11 @@ interface ConfigUpdateListener: ConfigListener, IHasConfigItems {
saveConfig() saveConfig()
} }
override fun onUnlockAllLiveCostumeChanged(value: Boolean) {
config.unlockAllLiveCostume = value
saveConfig()
}
override fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int) { override fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int) {
try { try {
val valueStr = s.toString() val valueStr = s.toString()

View File

@ -3,11 +3,12 @@ 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
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
@ -16,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"
@ -54,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 {
@ -230,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!!)
} }
} }
@ -247,6 +282,9 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
val gkmsData = intent.getStringExtra("gkmsData") val gkmsData = intent.getStringExtra("gkmsData")
val programData = intent.getStringExtra("localData") val programData = intent.getStringExtra("localData")
if (gkmsData != null) { if (gkmsData != null) {
val readVersion = intent.getStringExtra("lVerName")
checkPluginVersion(activity, readVersion)
gkmsDataInited = true gkmsDataInited = true
val initConfig = try { val initConfig = try {
json.decodeFromString<GakumasConfig>(gkmsData) json.decodeFromString<GakumasConfig>(gkmsData)
@ -282,11 +320,16 @@ 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) {
intent.getParcelableExtra("resource_file", Uri::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra<Uri>("resource_file")
}
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)
} }
@ -305,6 +348,40 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
} }
} }
private fun checkPluginVersion(activity: Activity, readVersion: String?) {
val buildVersionName = BuildConfig.VERSION_NAME
Log.i(TAG, "Checking Plugin Version: Build: $buildVersionName, Request: $readVersion")
if (readVersion?.trim() == buildVersionName.trim()) {
return
}
val builder = AlertDialog.Builder(activity)
val infoBuilder = AlertDialog.Builder(activity)
builder.setTitle("Warning")
builder.setCancelable(false)
builder.setMessage(when (getCurrentLanguage(activity)) {
"zh" -> "检测到插件版本不一致\n内置版本: $buildVersionName\n请求版本: $readVersion\n\n这可能是使用了 LSPatch 的集成模式,仅更新了插件本体,未重新修补游戏导致的。请使用 $readVersion 版本的插件重新修补或使用本地模式。"
else -> "Detected plugin version mismatch\nBuilt-in version: $buildVersionName\nRequested version: $readVersion\n\nThis may be caused by using the LSPatch integration mode, where only the plugin itself was updated without re-patching the game. Please re-patch the game using the $readVersion version of the plugin or use the local mode."
})
builder.setPositiveButton("OK") { dialog, _ ->
dialog.dismiss()
}
builder.setNegativeButton("Exit") { dialog, _ ->
dialog.dismiss()
activity.finishAffinity()
}
val dialog = builder.create()
infoBuilder.setOnCancelListener {
dialog.show()
}
dialog.show()
}
private fun showGetConfigFailedImpl(activity: Context, title: String, msg: String, infoButton: String, dlButton: String, okButton: String) { private fun showGetConfigFailedImpl(activity: Context, title: String, msg: String, infoButton: String, dlButton: String, okButton: String) {
if (getConfigError == null) return if (getConfigError == null) return
val builder = AlertDialog.Builder(activity) val builder = AlertDialog.Builder(activity)
@ -399,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)
@ -428,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())
@ -449,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

@ -645,7 +645,7 @@ class PatchActivity : ComponentActivity() {
val copyFilesCmd: MutableList<String> = mutableListOf() val copyFilesCmd: MutableList<String> = mutableListOf()
val movedFiles: MutableList<String> = mutableListOf() val movedFiles: MutableList<String> = mutableListOf()
savedFileNames.forEach { file -> savedFileNames.forEach { file ->
val movedFileName = "$installDS/${file}" val movedFileName = "\"$installDS/${file}\""
movedFiles.add(movedFileName) movedFiles.add(movedFileName)
val dlSaveFileName = File(targetDirectory, file) val dlSaveFileName = File(targetDirectory, file)
copyFilesCmd.add("$action ${dlSaveFileName.absolutePath} $movedFileName") copyFilesCmd.add("$action ${dlSaveFileName.absolutePath} $movedFileName")

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)
} }
} }
return "0.0"
} }
fun getInstalledVersion(): String { fun getInstalledVersion(): String {
@ -96,30 +91,29 @@ object FilesChecker {
val versionFile = File(pluginFilesDir, "version.txt") val versionFile = File(pluginFilesDir, "version.txt")
if (!versionFile.exists()) return "0.0" if (!versionFile.exists()) return "0.0"
return versionFile.readText() return versionFile.readText().trim()
} }
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 {
@ -146,6 +140,7 @@ object FilesChecker {
val genericTransDir = File(localFilesDir, "genericTrans") val genericTransDir = File(localFilesDir, "genericTrans")
val genericTransFile = File(localFilesDir, "generic.json") val genericTransFile = File(localFilesDir, "generic.json")
val i18nFile = File(localFilesDir, "localization.json") val i18nFile = File(localFilesDir, "localization.json")
val masterTransDir = File(localFilesDir, "masterTrans")
if (fontFile.exists()) { if (fontFile.exists()) {
fontFile.delete() fontFile.delete()
@ -156,6 +151,9 @@ object FilesChecker {
if (deleteRecursively(genericTransDir)) { if (deleteRecursively(genericTransDir)) {
genericTransDir.mkdirs() genericTransDir.mkdirs()
} }
if (deleteRecursively(masterTransDir)) {
masterTransDir.mkdirs()
}
if (genericTransFile.exists()) { if (genericTransFile.exists()) {
genericTransFile.writeText("{}") genericTransFile.writeText("{}")
} }

View File

@ -9,16 +9,20 @@ data class GakumasConfig (
var lazyInit: Boolean = true, var lazyInit: Boolean = true,
var replaceFont: Boolean = true, var replaceFont: Boolean = true,
var textTest: Boolean = false, var textTest: Boolean = false,
var useMasterTrans: Boolean = true,
var dumpText: Boolean = false, var dumpText: Boolean = false,
var gameOrientation: Int = 0, var gameOrientation: Int = 0,
var forceExportResource: Boolean = false, var forceExportResource: Boolean = false,
var enableFreeCamera: Boolean = false, var enableFreeCamera: Boolean = false,
var targetFrameRate: Int = 0, var targetFrameRate: Int = 0,
var unlockAllLive: Boolean = false, var unlockAllLive: Boolean = false,
var unlockAllLiveCostume: Boolean = false,
var enableLiveCustomeDress: Boolean = false, var enableLiveCustomeDress: Boolean = false,
var liveCustomeHeadId: String = "", var liveCustomeHeadId: String = "",
var liveCustomeCostumeId: String = "", var liveCustomeCostumeId: String = "",
var loginAsIOS: Boolean = false,
var useCustomeGraphicSettings: Boolean = false, var useCustomeGraphicSettings: Boolean = false,
var renderScale: Float = 0.77f, var renderScale: Float = 0.77f,
var qualitySettingsLevel: Int = 3, var qualitySettingsLevel: Int = 3,

View File

@ -75,6 +75,10 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
item { item {
GakuGroupBox(modifier, stringResource(R.string.debug_settings)) { GakuGroupBox(modifier, stringResource(R.string.debug_settings)) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
GakuSwitch(modifier, stringResource(R.string.useMasterDBTrans), checked = config.value.useMasterTrans) {
v -> context?.onUseMasterTransChanged(v)
}
GakuSwitch(modifier, stringResource(R.string.text_hook_test_mode), checked = config.value.textTest) { GakuSwitch(modifier, stringResource(R.string.text_hook_test_mode), checked = config.value.textTest) {
v -> context?.onTextTestChanged(v) v -> context?.onTextTestChanged(v)
} }
@ -86,6 +90,10 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
GakuSwitch(modifier, stringResource(R.string.force_export_resource), checked = config.value.forceExportResource) { GakuSwitch(modifier, stringResource(R.string.force_export_resource), checked = config.value.forceExportResource) {
v -> context?.onForceExportResourceChanged(v) v -> context?.onForceExportResourceChanged(v)
} }
GakuSwitch(modifier, stringResource(R.string.login_as_ios), checked = config.value.loginAsIOS) {
v -> context?.onLoginAsIOSChanged(v)
}
} }
} }
@ -353,6 +361,12 @@ fun AdvanceSettingsPage(modifier: Modifier = Modifier,
checked = config.value.unlockAllLive) { checked = config.value.unlockAllLive) {
v -> context?.onUnlockAllLiveChanged(v) v -> context?.onUnlockAllLiveChanged(v)
} }
GakuSwitch(modifier, stringResource(R.string.unlockAllLiveCostume),
checked = config.value.unlockAllLiveCostume) {
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)
@ -377,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

@ -314,8 +314,8 @@ fun HomePage(modifier: Modifier = Modifier,
fontSize = 14f, fontSize = 14f,
value = programConfig.value.useAPIAssetsURL, value = programConfig.value.useAPIAssetsURL,
onValueChange = { c -> context?.onPUseAPIAssetsURLChanged(c, 0, 0, 0)}, onValueChange = { c -> context?.onPUseAPIAssetsURLChanged(c, 0, 0, 0)},
label = { Text(stringResource(R.string.api_addr)) }, label = { Text(stringResource(R.string.api_addr)) }
keyboardOptions = keyboardOptionsNumber) )
if (downloadAble) { if (downloadAble) {
GakuButton(modifier = modifier GakuButton(modifier = modifier
@ -411,8 +411,8 @@ fun HomePage(modifier: Modifier = Modifier,
fontSize = 14f, fontSize = 14f,
value = programConfig.value.transRemoteZipUrl, value = programConfig.value.transRemoteZipUrl,
onValueChange = { c -> context?.onPTransRemoteZipUrlChanged(c, 0, 0, 0)}, onValueChange = { c -> context?.onPTransRemoteZipUrlChanged(c, 0, 0, 0)},
label = { Text(stringResource(id = R.string.resource_url)) }, label = { Text(stringResource(id = R.string.resource_url)) }
keyboardOptions = keyboardOptionsNumber) )
if (downloadAble) { if (downloadAble) {
GakuButton(modifier = modifier GakuButton(modifier = modifier

View File

@ -1,4 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="abc_action_bar_home_description">ホームに戻る</string>
<string name="abc_action_bar_up_description">前に戻る</string>
<string name="abc_action_menu_overflow_description">その他のオプション</string>
<string name="abc_action_mode_done">完了</string>
<string name="abc_activity_chooser_view_see_all">すべて表示</string>
<string name="abc_activitychooserview_choose_application">アプリの選択</string>
<string name="abc_capital_off">OFF</string>
<string name="abc_capital_on">ON</string>
<string name="abc_menu_alt_shortcut_label">Alt+</string>
<string name="abc_menu_ctrl_shortcut_label">Ctrl+</string>
<string name="abc_menu_delete_shortcut_label">Delete</string>
<string name="abc_menu_enter_shortcut_label">Enter</string>
<string name="abc_menu_function_shortcut_label">Function+</string>
<string name="abc_menu_meta_shortcut_label">Meta+</string>
<string name="abc_menu_shift_shortcut_label">Shift+</string>
<string name="abc_menu_space_shortcut_label">Space</string>
<string name="abc_menu_sym_shortcut_label">Sym+</string>
<string name="abc_prepend_shortcut_label">Menu+</string>
<string name="abc_search_hint">検索…</string>
<string name="abc_searchview_description_clear">検索キーワードを削除</string>
<string name="abc_searchview_description_query">検索キーワード</string>
<string name="abc_searchview_description_search">検索</string>
<string name="abc_searchview_description_submit">検索キーワードを送信</string>
<string name="abc_searchview_description_voice">音声検索</string>
<string name="abc_shareactionprovider_share_with">共有</string>
<string name="abc_shareactionprovider_share_with_application">%sと共有</string>
<string name="abc_toolbar_collapse_description">折りたたむ</string>
<string name="about">情報</string> <string name="about">情報</string>
<string name="about_about_p1">このプラグインは完全に無料で提供されます。このプラグインで料金を支払ってしまった場合は、販売者に報告をしてください。</string> <string name="about_about_p1">このプラグインは完全に無料で提供されます。このプラグインで料金を支払ってしまった場合は、販売者に報告をしてください。</string>
<string name="about_about_p2">プラグインの QQ グループ: 975854705</string> <string name="about_about_p2">プラグインの QQ グループ: 975854705</string>
@ -8,8 +36,10 @@
<string name="about_warn_p2">外部プラグインは関連する TOS に違反するため、自己責任でご使用ください。</string> <string name="about_warn_p2">外部プラグインは関連する TOS に違反するため、自己責任でご使用ください。</string>
<string name="about_warn_title">警告</string> <string name="about_warn_title">警告</string>
<string name="advanced_settings">高度な設定</string> <string name="advanced_settings">高度な設定</string>
<string name="api_addr">APIアドレス (GitHub の最新リリース API)</string> <string name="androidx_startup">androidx.startup</string>
<string name="api_addr">API アドレス (GitHub 最新リリース API)</string>
<string name="app_name">Gakumas Localify</string> <string name="app_name">Gakumas Localify</string>
<string name="appbar_scrolling_view_behavior">com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior</string>
<string name="average">平均</string> <string name="average">平均</string>
<string name="axisx_x">X 軸.x</string> <string name="axisx_x">X 軸.x</string>
<string name="axisx_y">X 軸.y</string> <string name="axisx_y">X 軸.y</string>
@ -18,15 +48,28 @@
<string name="axisz_x">Z 軸.x</string> <string name="axisz_x">Z 軸.x</string>
<string name="axisz_y">Z 軸.y</string> <string name="axisz_y">Z 軸.y</string>
<string name="basic_settings">基本設定</string> <string name="basic_settings">基本設定</string>
<string name="bottom_sheet_behavior">com.google.android.material.bottomsheet.BottomSheetBehavior</string>
<string name="bottomsheet_action_collapse">ボトムシートを閉じる</string>
<string name="bottomsheet_action_expand">ボトムシートを開く</string>
<string name="bottomsheet_action_expand_halfway">下半分を展開</string>
<string name="bottomsheet_drag_handle_clicked">ハンドルをダブルタップしてドラッグ</string>
<string name="bottomsheet_drag_handle_content_description">ドラッグハンドル</string>
<string name="breast_param">胸のパラメーター</string> <string name="breast_param">胸のパラメーター</string>
<string name="breast_scale">胸の大きさ</string> <string name="breast_scale">胸の大きさ</string>
<string name="call_notification_answer_action">応答</string>
<string name="call_notification_answer_video_action">動画</string>
<string name="call_notification_decline_action">拒否</string>
<string name="call_notification_hang_up_action">通話終了</string>
<string name="call_notification_incoming_text">着信</string>
<string name="call_notification_ongoing_text">通話中</string>
<string name="call_notification_screening_text">着信をスクリーニング中</string>
<string name="camera_settings">カメラ設定</string> <string name="camera_settings">カメラ設定</string>
<string name="cancel">キャンセル</string> <string name="cancel">キャンセル</string>
<string name="character_counter_content_description">%1$d の %2$d に入力された文字</string> <string name="character_counter_content_description">%1$d の %2$d に入力された文字</string>
<string name="character_counter_overflowed_content_description">文字制限が %2$d 文字中、 %1$d 文字を超えています</string> <string name="character_counter_overflowed_content_description">文字制限が %2$d 文字中、 %1$d 文字を超えています</string>
<string name="character_counter_pattern">%1$d/%2$d</string> <string name="character_counter_pattern">%1$d/%2$d</string>
<string name="check_built_in_resource">内蔵アセットのアップデートを確認</string> <string name="check_built_in_resource">内蔵アセットの更新を確認</string>
<string name="check_resource_from_api">リソースアップデートを API から確認</string> <string name="check_resource_from_api">リソースの更新を API から確認</string>
<string name="check_update">確認</string> <string name="check_update">確認</string>
<string name="clear_text_end_icon_content_description">テキストを消去</string> <string name="clear_text_end_icon_content_description">テキストを消去</string>
<string name="close_drawer">ナビゲーションメニューを閉じる</string> <string name="close_drawer">ナビゲーションメニューを閉じる</string>
@ -37,7 +80,7 @@
<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>
<string name="default_error_message">入力が無効です</string> <string name="default_error_message">入力が無効です</string>
<string name="default_popup_window_title">ポップアップウィンドウ</string> <string name="default_popup_window_title">ポップアップウィンドウ</string>
<string name="del_remote_after_update">キャッシュファイルをアップデート後に削除</string> <string name="del_remote_after_update">キャッシュファイルを更新後に削除</string>
<string name="delete_plugin_resource">プラグインリソースを削除</string> <string name="delete_plugin_resource">プラグインリソースを削除</string>
<string name="download">ダウンロード</string> <string name="download">ダウンロード</string>
<string name="downloaded_resource_version">ダウンロードされたバージョン</string> <string name="downloaded_resource_version">ダウンロードされたバージョン</string>
@ -49,16 +92,19 @@
<string name="error_icon_content_description">エラー</string> <string name="error_icon_content_description">エラー</string>
<string name="export_text">テキストをエクスポート</string> <string name="export_text">テキストをエクスポート</string>
<string name="exposed_dropdown_menu_content_description">ドロップダウンメニューを表示</string> <string name="exposed_dropdown_menu_content_description">ドロップダウンメニューを表示</string>
<string name="force_export_resource">リソースのアップデートを強制する</string> <string name="fab_transformation_scrim_behavior">com.google.android.material.transformation.FabTransformationScrimBehavior</string>
<string name="fab_transformation_sheet_behavior">com.google.android.material.transformation.FabTransformationSheetBehavior</string>
<string name="force_export_resource">リソースの更新を強制する</string>
<string name="gakumas_localify">Gakumas Localify</string> <string name="gakumas_localify">Gakumas Localify</string>
<string name="game_patch">ゲームパッチ</string> <string name="game_patch">ゲームパッチ</string>
<string name="graphic_settings">グラフィック設定</string> <string name="graphic_settings">グラフィック設定</string>
<string name="hide_bottom_view_on_scroll_behavior">com.google.android.material.behavior.HideBottomViewOnScrollBehavior</string>
<string name="hign"></string> <string name="hign"></string>
<string name="home">ホーム</string> <string name="home">ホーム</string>
<string name="home_shizuku_warning">一部の機能が使用できません</string> <string name="home_shizuku_warning">一部の機能が使用できません</string>
<string name="icon_content_description">ダイアログアイコン</string> <string name="icon_content_description">ダイアログアイコン</string>
<string name="in_progress">実行中</string> <string name="in_progress">実行中</string>
<string name="indeterminate">部分的にチェック済み</string> <string name="indeterminate">部分的に確認済み</string>
<string name="install">インストール</string> <string name="install">インストール</string>
<string name="installing">インストール中</string> <string name="installing">インストール中</string>
<string name="invalid_zip_file">無効なファイル</string> <string name="invalid_zip_file">無効なファイル</string>
@ -69,9 +115,168 @@
<string name="liveUseCustomeDress">ライブのキャラクターをカスタム</string> <string name="liveUseCustomeDress">ライブのキャラクターをカスタム</string>
<string name="live_costume_head_id">ライブのカスタムヘッド ID (例: costume_head_hski-cstm-0002)</string> <string name="live_costume_head_id">ライブのカスタムヘッド ID (例: costume_head_hski-cstm-0002)</string>
<string name="live_custome_dress_id">ライブ衣装のカスタム ID (例: hski-cstm-0002)</string> <string name="live_custome_dress_id">ライブ衣装のカスタム ID (例: hski-cstm-0002)</string>
<string name="login_as_ios">iOS としてログイン</string>
<string name="low"></string> <string name="low"></string>
<string name="m3_exceed_max_badge_text_suffix">%1$s%2$s</string>
<string name="m3_ref_typeface_brand_medium">sans-serif-medium</string>
<string name="m3_ref_typeface_brand_regular">sans-serif</string>
<string name="m3_ref_typeface_plain_medium">sans-serif-medium</string>
<string name="m3_ref_typeface_plain_regular">sans-serif</string>
<string name="m3_sys_motion_easing_emphasized">path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1)</string>
<string name="m3_sys_motion_easing_emphasized_accelerate">cubic-bezier(0.3, 0, 0.8, 0.2)</string>
<string name="m3_sys_motion_easing_emphasized_decelerate">cubic-bezier(0.1, 0.7, 0.1, 1)</string>
<string name="m3_sys_motion_easing_emphasized_path_data">M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1</string>
<string name="m3_sys_motion_easing_legacy">cubic-bezier(0.4, 0, 0.2, 1)</string>
<string name="m3_sys_motion_easing_legacy_accelerate">cubic-bezier(0.4, 0, 1, 1)</string>
<string name="m3_sys_motion_easing_legacy_decelerate">cubic-bezier(0, 0, 0.2, 1)</string>
<string name="m3_sys_motion_easing_linear">cubic-bezier(0, 0, 1, 1)</string>
<string name="m3_sys_motion_easing_standard">cubic-bezier(0.2, 0, 0, 1)</string>
<string name="m3_sys_motion_easing_standard_accelerate">cubic-bezier(0.3, 0, 1, 1)</string>
<string name="m3_sys_motion_easing_standard_decelerate">cubic-bezier(0, 0, 0, 1)</string>
<string name="m3c_bottom_sheet_collapse_description">ボトムシートを折りたたみます</string>
<string name="m3c_bottom_sheet_dismiss_description">ボトムシートを閉じます</string>
<string name="m3c_bottom_sheet_drag_handle_description">ドラッグハンドル</string>
<string name="m3c_bottom_sheet_expand_description">ボトムシートを開きます</string>
<string name="m3c_bottom_sheet_pane_title">ボトムシート</string>
<string name="m3c_date_input_headline">入力された日付</string>
<string name="m3c_date_input_headline_description">入力された日付: %1$s</string>
<string name="m3c_date_input_invalid_for_pattern">想定パターンと一致しない日付: %1$s</string>
<string name="m3c_date_input_invalid_not_allowed">許可されない日付: %1$s</string>
<string name="m3c_date_input_invalid_year_range">想定される年の範囲(%1$s%2$sから日付が外れています</string>
<string name="m3c_date_input_label">日付</string>
<string name="m3c_date_input_no_input_description">なし</string>
<string name="m3c_date_input_title">日付の選択</string>
<string name="m3c_date_picker_headline">選択した日付</string>
<string name="m3c_date_picker_headline_description">現在の選択: %1$s</string>
<string name="m3c_date_picker_navigate_to_year_description">年に移動 %1$s</string>
<string name="m3c_date_picker_no_selection_description">なし</string>
<string name="m3c_date_picker_scroll_to_earlier_years">これより前の年を表示するにはスクロールしてください</string>
<string name="m3c_date_picker_scroll_to_later_years">これより後の年を表示するにはスクロールしてください</string>
<string name="m3c_date_picker_switch_to_calendar_mode">カレンダー入力モードに切り替え</string>
<string name="m3c_date_picker_switch_to_day_selection">スワイプして年を選択するか、タップして日付の選択に戻ります</string>
<string name="m3c_date_picker_switch_to_input_mode">テキスト入力モードに切り替え</string>
<string name="m3c_date_picker_switch_to_next_month">翌月に変更</string>
<string name="m3c_date_picker_switch_to_previous_month">前月に変更</string>
<string name="m3c_date_picker_switch_to_year_selection">年の選択に切り替え</string>
<string name="m3c_date_picker_title">日付の選択</string>
<string name="m3c_date_picker_today_description">今日</string>
<string name="m3c_date_picker_year_picker_pane_title">年の選択ツールの表示</string>
<string name="m3c_date_range_input_invalid_range_input">入力された期間は無効です</string>
<string name="m3c_date_range_input_title">日付の入力</string>
<string name="m3c_date_range_picker_day_in_range">範囲内</string>
<string name="m3c_date_range_picker_end_headline">終了日</string>
<string name="m3c_date_range_picker_scroll_to_next_month">翌月を表示するにはスクロールしてください</string>
<string name="m3c_date_range_picker_scroll_to_previous_month">前月を表示するにはスクロールしてください</string>
<string name="m3c_date_range_picker_start_headline">開始日</string>
<string name="m3c_date_range_picker_title">日付の選択</string>
<string name="m3c_dialog">ダイアログ</string>
<string name="m3c_dropdown_menu_collapsed">閉じています</string>
<string name="m3c_dropdown_menu_expanded">開いています</string>
<string name="m3c_search_bar_search">検索</string>
<string name="m3c_snackbar_dismiss">閉じる</string>
<string name="m3c_suggestions_available">検索候補は次のとおりです</string>
<string name="m3c_time_picker_am">AM</string>
<string name="m3c_time_picker_hour">時間</string>
<string name="m3c_time_picker_hour_24h_suffix">%1$d 時間</string>
<string name="m3c_time_picker_hour_selection">時刻を選択</string>
<string name="m3c_time_picker_hour_suffix">"%1$d 時"</string>
<string name="m3c_time_picker_hour_text_field">(時間単位)</string>
<string name="m3c_time_picker_minute"></string>
<string name="m3c_time_picker_minute_selection">分を選択</string>
<string name="m3c_time_picker_minute_suffix">%1$d 分</string>
<string name="m3c_time_picker_minute_text_field">(分単位)</string>
<string name="m3c_time_picker_period_toggle_description">午前または午後を選択</string>
<string name="m3c_time_picker_pm">PM</string>
<string name="m3c_tooltip_long_press_label">ツールチップを表示</string>
<string name="m3c_tooltip_pane_description">ツールチップ</string>
<string name="material_clock_display_divider">:</string>
<string name="material_clock_toggle_content_description">午前または午後を選択</string>
<string name="material_hour_24h_suffix">%1$s 時間</string>
<string name="material_hour_selection">時刻を選択してください</string>
<string name="material_hour_suffix">"%1$s時"</string>
<string name="material_minute_selection">分を選択</string>
<string name="material_minute_suffix">%1$s分</string>
<string name="material_motion_easing_accelerated">cubic-bezier(0.4, 0.0, 1.0, 1.0)</string>
<string name="material_motion_easing_decelerated">cubic-bezier(0.0, 0.0, 0.2, 1.0)</string>
<string name="material_motion_easing_emphasized">path(M 0,0 C 0.05, 0, 0.133333, 0.06, 0.166666, 0.4 C 0.208333, 0.82, 0.25, 1, 1, 1)</string>
<string name="material_motion_easing_linear">cubic-bezier(0.0, 0.0, 1.0, 1.0)</string>
<string name="material_motion_easing_standard">cubic-bezier(0.4, 0.0, 0.2, 1.0)</string>
<string name="material_slider_range_end">Range end</string>
<string name="material_slider_range_start">Range start</string>
<string name="material_slider_value">Value</string>
<string name="material_timepicker_am">AM</string>
<string name="material_timepicker_clock_mode_description">時刻を時計で入力するモードに切り替えます。</string>
<string name="material_timepicker_hour">時間</string>
<string name="material_timepicker_minute"></string>
<string name="material_timepicker_pm">PM</string>
<string name="material_timepicker_select_time">時間を選択</string>
<string name="material_timepicker_text_input_mode_description">時刻をテキストで入力するモードに切り替えます。</string>
<string name="max_high">ウルトラ</string> <string name="max_high">ウルトラ</string>
<string name="middle"></string> <string name="middle"></string>
<string name="mtrl_badge_numberless_content_description">新しい通知</string>
<string name="mtrl_checkbox_button_icon_path_checked">M14,18.2 11.4,15.6 10,17 14,21 22,13 20.6,11.6z</string>
<string name="mtrl_checkbox_button_icon_path_group_name">icon</string>
<string name="mtrl_checkbox_button_icon_path_indeterminate">M13.4,15 11,15 11,17 13.4,17 21,17 21,15z</string>
<string name="mtrl_checkbox_button_icon_path_name">icon path</string>
<string name="mtrl_checkbox_button_path_checked">M23,7H9C7.9,7,7,7.9,7,9v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V9C25,7.9,24.1,7,23,7z</string>
<string name="mtrl_checkbox_button_path_group_name">button</string>
<string name="mtrl_checkbox_button_path_name">button path</string>
<string name="mtrl_checkbox_button_path_unchecked">M23,7H9C7.9,7,7,7.9,7,9v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V9C25,7.9,24.1,7,23,7z M23,23H9V9h14V23z</string>
<string name="mtrl_checkbox_state_description_checked">オン</string>
<string name="mtrl_checkbox_state_description_indeterminate">一部オン</string>
<string name="mtrl_checkbox_state_description_unchecked">オフ</string>
<string name="mtrl_chip_close_icon_content_description">%1$s を削除します</string>
<string name="mtrl_exceed_max_badge_number_content_description">%1$d 件以上の新しい通知</string>
<string name="mtrl_exceed_max_badge_number_suffix">%1$d%2$s</string>
<string name="mtrl_picker_a11y_next_month">翌月に変更</string>
<string name="mtrl_picker_a11y_prev_month">前月に変更</string>
<string name="mtrl_picker_announce_current_range_selection">開始日の選択: %1$s 終了日の選択: %2$s</string>
<string name="mtrl_picker_announce_current_selection">現在の選択: %1$s</string>
<string name="mtrl_picker_announce_current_selection_none">なし</string>
<string name="mtrl_picker_cancel">キャンセル</string>
<string name="mtrl_picker_confirm">OK</string>
<string name="mtrl_picker_date_header_selected">%1$s</string>
<string name="mtrl_picker_date_header_title">日付を選択してください</string>
<string name="mtrl_picker_date_header_unselected">選択した日付</string>
<string name="mtrl_picker_day_of_week_column_header">%1$s</string>
<string name="mtrl_picker_end_date_description">終了日 %1$s</string>
<string name="mtrl_picker_invalid_format">形式が無効です。</string>
<string name="mtrl_picker_invalid_format_example">例: %1$s</string>
<string name="mtrl_picker_invalid_format_use">使用: %1$s</string>
<string name="mtrl_picker_invalid_range">範囲が無効です。</string>
<string name="mtrl_picker_navigate_to_current_year_description">現在の年(%1$dに移動</string>
<string name="mtrl_picker_navigate_to_year_description">%1$d 年に移動</string>
<string name="mtrl_picker_out_of_range">範囲外: %1$s</string>
<string name="mtrl_picker_range_header_only_end_selected">開始日~%1$s</string>
<string name="mtrl_picker_range_header_only_start_selected">%1$s終了日</string>
<string name="mtrl_picker_range_header_selected">%1$s%2$s</string>
<string name="mtrl_picker_range_header_title">期間を選択してください</string>
<string name="mtrl_picker_range_header_unselected">開始日~終了日</string>
<string name="mtrl_picker_save">保存</string>
<string name="mtrl_picker_start_date_description">開始日 %1$s</string>
<string name="mtrl_picker_text_input_date_hint">日付</string>
<string name="mtrl_picker_text_input_date_range_end_hint">終了日</string>
<string name="mtrl_picker_text_input_date_range_start_hint">開始日</string>
<string name="mtrl_picker_text_input_day_abbr">d</string>
<string name="mtrl_picker_text_input_month_abbr">m</string>
<string name="mtrl_picker_text_input_year_abbr">y</string>
<string name="mtrl_picker_today_description">今日(%1$s</string>
<string name="mtrl_picker_toggle_to_calendar_input_mode">カレンダー入力モードに切り替え</string>
<string name="mtrl_picker_toggle_to_day_selection">タップするとカレンダー表示に切り替わります</string>
<string name="mtrl_picker_toggle_to_text_input_mode">テキスト入力モードに切り替え</string>
<string name="mtrl_picker_toggle_to_year_selection">タップすると年表示に切り替わります</string>
<string name="mtrl_switch_thumb_group_name">circle_group</string>
<string name="mtrl_switch_thumb_path_checked">M4,16 A12,12 0 0,1 16,4 H16 A12,12 0 0,1 16,28 H16 A12,12 0 0,1 4,16</string>
<string name="mtrl_switch_thumb_path_morphing">M0,16 A11,11 0 0,1 11,5 H21 A11,11 0 0,1 21,27 H11 A11,11 0 0,1 0,16</string>
<string name="mtrl_switch_thumb_path_name">circle</string>
<string name="mtrl_switch_thumb_path_pressed">M2,16 A14,14 0 0,1 16,2 H16 A14,14 0 0,1 16,30 H16 A14,14 0 0,1 2,16</string>
<string name="mtrl_switch_thumb_path_unchecked">M8,16 A8,8 0 0,1 16,8 H16 A8,8 0 0,1 16,24 H16 A8,8 0 0,1 8,16</string>
<string name="mtrl_switch_track_decoration_path">M1,16 A15,15 0 0,1 16,1 H36 A15,15 0 0,1 36,31 H16 A15,15 0 0,1 1,16</string>
<string name="mtrl_switch_track_path">M0,16 A16,16 0 0,1 16,0 H36 A16,16 0 0,1 36,32 H16 A16,16 0 0,1 0,16</string>
<string name="mtrl_timepicker_cancel">キャンセル</string>
<string name="mtrl_timepicker_confirm">OK</string>
<string name="navigation_menu">ナビゲーションメニュー</string>
<string name="not_selected">未選択</string>
<string name="off">OFF</string> <string name="off">OFF</string>
<string name="ok">OK</string> <string name="ok">OK</string>
<string name="on">ON</string> <string name="on">ON</string>
@ -83,12 +288,23 @@
<string name="patch_debuggable">デバッグを可能にする</string> <string name="patch_debuggable">デバッグを可能にする</string>
<string name="patch_finished">パッチが完了しました。インストールをしますか?</string> <string name="patch_finished">パッチが完了しました。インストールをしますか?</string>
<string name="patch_integrated">統合</string> <string name="patch_integrated">統合</string>
<string name="patch_integrated_desc">"モジュールを埋め込んだ状態なアプリでパッチを当てます。\nパッチを適用したアプリは LSPatch Manager なしで実行できますが、動的に管理はできません。\n統合パッチが適用されたアプリは、LSPatch Manager がインストールされていないデバイスでも使用が可能です。"</string> <string name="patch_integrated_desc">"モジュールを埋め込んだ状態なアプリでパッチを当てます。
パッチを適用したアプリは LSPatch Manager なしで実行できますが、動的に管理はできません。
統合パッチが適用されたアプリは、LSPatch Manager がインストールされていないデバイスでも使用が可能です。"</string>
<string name="patch_local">ローカル</string> <string name="patch_local">ローカル</string>
<string name="patch_local_desc">"モジュールを埋め込まずにアプリにパッチを当てます。\nXposed スコープは再パッチなしで動的に変更が可能です。\nローカルでのパッチを当てたアプリは、ローカルのデバイスでのみ実行可能です。"</string> <string name="patch_local_desc">"モジュールを埋め込まずにアプリにパッチを当てます。
Xposed スコープは再パッチなしで動的に変更が可能です。
ローカルでのパッチを当てたアプリは、ローカルのデバイスでのみ実行可能です。"</string>
<string name="patch_mode">パッチモード</string> <string name="patch_mode">パッチモード</string>
<string name="patch_uninstall_confirm">アンインストールをしてもよろしいですか?</string> <string name="patch_uninstall_confirm">アンインストールをしてもよろしいですか?</string>
<string name="patch_uninstall_text">"署名が異なるため、パッチをインストールする前に元となるアプリをアンインストールする必要があります。\n個人データのバックアップを設定済みであることを確認してください。"</string> <string name="patch_uninstall_text">"署名が異なるため、パッチをインストールする前に元となるアプリをアンインストールする必要があります。
個人データのバックアップを設定済みであることを確認してください。"</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_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_strike_through">M3.27,4.27 L19.74,20.74</string>
<string name="pendulum">揺れ</string> <string name="pendulum">揺れ</string>
<string name="pendulumrange">揺れの範囲</string> <string name="pendulumrange">揺れの範囲</string>
<string name="plugin_code">プラグインのコード</string> <string name="plugin_code">プラグインのコード</string>
@ -100,24 +316,37 @@
<string name="reserve_patched">パッチ済みの APK を予約する</string> <string name="reserve_patched">パッチ済みの APK を予約する</string>
<string name="resource_settings">リソース設定</string> <string name="resource_settings">リソース設定</string>
<string name="resource_url">リソース URL</string> <string name="resource_url">リソース URL</string>
<string name="rootweight">ルートウェイト</string> <string name="rootweight">ルートウエイト</string>
<string name="search_menu_title">検索</string>
<string name="searchbar_scrolling_view_behavior">com.google.android.material.search.SearchBar$ScrollingViewBehavior</string>
<string name="searchview_clear_text_content_description">テキストを消去</string>
<string name="searchview_navigation_content_description">戻る</string>
<string name="selected">選択済み</string> <string name="selected">選択済み</string>
<string name="setFpsTitle">最大 FPS (0 はオリジナルの設定を使用します)</string> <string name="setFpsTitle">最大 FPS (0 はオリジナルの設定を使用します)</string>
<string name="shizuku_available">Shizuku サービスが有効です</string> <string name="shizuku_available">Shizuku サービスが有効です</string>
<string name="shizuku_unavailable">Shizuku サービスが接続されていません</string> <string name="shizuku_unavailable">Shizuku サービスが接続されていません</string>
<string name="side_sheet_accessibility_pane_title">サイドシート</string>
<string name="side_sheet_behavior">com.google.android.material.sidesheet.SideSheetBehavior</string>
<string name="spring">跳ね</string> <string name="spring">跳ね</string>
<string name="start_game">ゲーム開始 / ホットリロードの設定</string> <string name="start_game">ゲーム開始 / ホットリロードの設定</string>
<string name="status_bar_notification_info_overflow">999+</string>
<string name="stiffness">剛性</string> <string name="stiffness">剛性</string>
<string name="support_file_types">"対応ファイル:\n単一または複数選択: apk\n単一選択: apks、xapk、zip"</string> <string name="support_file_types">"対応ファイル:
単一または複数選択: apk
単一選択: apks、xapk、zip"</string>
<string name="switch_role">切り替え</string> <string name="switch_role">切り替え</string>
<string name="tab">タブ</string> <string name="tab">タブ</string>
<string name="template_percent">%1$d パーセント。</string> <string name="template_percent">%1$d パーセント。</string>
<string name="test_mode_live">テストモード - ライブ</string> <string name="test_mode_live">テストモード - ライブ</string>
<string name="text_hook_test_mode">テキストフックテストモード</string> <string name="text_hook_test_mode">テキストフックテストモード</string>
<string name="tooltip_description">ツールチップ</string>
<string name="tooltip_label">ツールチップを表示</string>
<string name="translation_repository">翻訳のリポジトリ</string> <string name="translation_repository">翻訳のリポジトリ</string>
<string name="translation_resource_update">翻訳リソースをアップデート</string> <string name="translation_resource_update">翻訳リソースを更新</string>
<string name="unlockAllLive">すべてのライブを開放</string> <string name="unlockAllLive">すべてのライブを開放</string>
<string name="unlockAllLiveCostume">すべてのライブ衣装を開放</string>
<string name="useCustomeGraphicSettings">カスタムグラフィック設定を使用する</string> <string name="useCustomeGraphicSettings">カスタムグラフィック設定を使用する</string>
<string name="useMasterDBTrans">MasterDB のローカライズを有効化</string>
<string name="use_remote_zip_resource">リモート ZIP リソースを使用する</string> <string name="use_remote_zip_resource">リモート ZIP リソースを使用する</string>
<string name="usearmcorrection">Arm コレクションを使用する</string> <string name="usearmcorrection">Arm コレクションを使用する</string>
<string name="uselimit_0_1">リミットレンジの倍率 (0 は無制限)</string> <string name="uselimit_0_1">リミットレンジの倍率 (0 は無制限)</string>

View File

@ -8,14 +8,17 @@
<string name="start_game">以上述配置启动游戏/重载配置</string> <string name="start_game">以上述配置启动游戏/重载配置</string>
<string name="setFpsTitle">最大 FPS (0 为保持游戏原设置)</string> <string name="setFpsTitle">最大 FPS (0 为保持游戏原设置)</string>
<string name="unlockAllLive">解锁所有 Live</string> <string name="unlockAllLive">解锁所有 Live</string>
<string name="unlockAllLiveCostume">解锁所有 Live 服装</string>
<string name="liveUseCustomeDress">Live 使用自定义角色</string> <string name="liveUseCustomeDress">Live 使用自定义角色</string>
<string name="live_costume_head_id">Live 自定义头部 ID (例: costume_head_hski-cstm-0002)</string> <string name="live_costume_head_id">Live 自定义头部 ID (例: costume_head_hski-cstm-0002)</string>
<string name="live_custome_dress_id">Live 自定义服装 ID (例: hski-cstm-0002)</string> <string name="live_custome_dress_id">Live 自定义服装 ID (例: hski-cstm-0002)</string>
<string name="useCustomeGraphicSettings">使用自定义画质设置</string> <string name="useCustomeGraphicSettings">使用自定义画质设置</string>
<string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string> <string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string>
<string name="text_hook_test_mode">文本 hook 测试模式</string> <string name="text_hook_test_mode">文本 hook 测试模式</string>
<string name="useMasterDBTrans">使用 MasterDB 本地化</string>
<string name="export_text">导出文本</string> <string name="export_text">导出文本</string>
<string name="force_export_resource">启动后强制导出资源</string> <string name="force_export_resource">启动后强制导出资源</string>
<string name="login_as_ios">以 iOS 登陆</string>
<string name="max_high">极高</string> <string name="max_high">极高</string>
<string name="very_high">超高</string> <string name="very_high">超高</string>
<string name="hign"></string> <string name="hign"></string>
@ -99,6 +102,8 @@
<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>

View File

@ -8,14 +8,17 @@
<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>
<string name="unlockAllLive">Unlock All Live</string> <string name="unlockAllLive">Unlock All Live</string>
<string name="unlockAllLiveCostume">Unlock All Live Costume</string>
<string name="liveUseCustomeDress">Live Custom Character</string> <string name="liveUseCustomeDress">Live Custom Character</string>
<string name="live_costume_head_id">Live Custom Head ID (eg. costume_head_hski-cstm-0002)</string> <string name="live_costume_head_id">Live Custom Head ID (eg. costume_head_hski-cstm-0002)</string>
<string name="live_custome_dress_id">Live Custom Dress ID (eg. hski-cstm-0002)</string> <string name="live_custome_dress_id">Live Custom Dress ID (eg. hski-cstm-0002)</string>
<string name="useCustomeGraphicSettings">Use Custom Graphics Settings</string> <string name="useCustomeGraphicSettings">Use Custom Graphics Settings</string>
<string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string> <string name="renderscale">RenderScale (0.5/0.59/0.67/0.77/1.0)</string>
<string name="text_hook_test_mode">Text Hook Test Mode</string> <string name="text_hook_test_mode">Text Hook Test Mode</string>
<string name="useMasterDBTrans">Enable MasterDB Localization</string>
<string name="export_text">Export Text</string> <string name="export_text">Export Text</string>
<string name="force_export_resource">Force Update Resource</string> <string name="force_export_resource">Force Update Resource</string>
<string name="login_as_ios">Login as iOS</string>
<string name="max_high">Ultra</string> <string name="max_high">Ultra</string>
<string name="very_high">Very High</string> <string name="very_high">Very High</string>
<string name="hign">High</string> <string name="hign">High</string>
@ -45,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>
@ -99,6 +102,8 @@
<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>

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"
@ -17,7 +17,7 @@ lifecycle = "2.8.2"
material = "1.12.0" material = "1.12.0"
navigationCompose = "2.7.7" navigationCompose = "2.7.7"
xdl = "2.1.1" xdl = "2.1.1"
shadowhook = "1.0.9" shadowhook = "1.0.10"
serialization="1.7.1" serialization="1.7.1"
zip4j = "2.9.1" zip4j = "2.9.1"
@ -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/" }
} }
} }