Compare commits

..

No commits in common. "a12651d60fc3b50486f38afc46aa9673a008f099" and "c7af3e41a523b27b619d09c24d64f151f1ccb61e" have entirely different histories.

13 changed files with 262 additions and 252 deletions

View File

@ -130,6 +130,6 @@ dependencies {
implementation(libs.xdl) implementation(libs.xdl)
implementation(libs.shadowhook) implementation(libs.shadowhook)
compileOnly(libs.libxposed.api) compileOnly(libs.xposed.api)
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
} }

View File

@ -21,6 +21,23 @@
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

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

View File

@ -3,7 +3,7 @@ package io.github.chinosk.gakumas.localify
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.app.Application import android.app.AndroidAppHelper
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
@ -17,32 +17,34 @@ 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 io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater import de.robv.android.xposed.IXposedHookLoadPackage
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 : XposedModule() { class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
private var modulePath: String = "" private lateinit var modulePath: String
private var nativeLibLoadSuccess: Boolean = false private var nativeLibLoadSuccess: Boolean
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"
@ -53,187 +55,160 @@ class GakumasHookMain : XposedModule() {
private var externalFilesChecked: Boolean = false private var externalFilesChecked: Boolean = false
private var gameActivity: Activity? = null private var gameActivity: Activity? = null
override fun onModuleLoaded(param: XposedModuleInterface.ModuleLoadedParam) { override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
modulePath = getModuleApplicationInfo().sourceDir // if (lpparam.packageName == "io.github.chinosk.gakumas.localify") {
// 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}")
// }
// }
// )
// }
ShadowHook.init( if (lpparam.packageName != targetPackageName) {
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
} }
val classLoader = param.classLoader XposedHelpers.findAndHookMethod(
"android.app.Activity",
hookMethod( lpparam.classLoader,
classLoader = classLoader, "dispatchKeyEvent",
className = "android.app.Activity", KeyEvent::class.java,
methodName = "dispatchKeyEvent", object : XC_MethodHook() {
parameterTypes = arrayOf(KeyEvent::class.java), override fun beforeHookedMethod(param: MethodHookParam) {
before = { chain -> val keyEvent = param.args[0] as KeyEvent
val keyEvent = chain.getArg(0) as KeyEvent val keyCode = keyEvent.keyCode
keyboardEvent(keyEvent.keyCode, keyEvent.action) val action = keyEvent.action
// Log.d(TAG, "Key event: keyCode=$keyCode, action=$action")
keyboardEvent(keyCode, action)
}
} }
) )
hookMethod( XposedHelpers.findAndHookMethod(
classLoader = classLoader, "android.app.Activity",
className = "android.app.Activity", lpparam.classLoader,
methodName = "dispatchGenericMotionEvent", "dispatchGenericMotionEvent",
parameterTypes = arrayOf(MotionEvent::class.java), MotionEvent::class.java,
before = { chain -> object : XC_MethodHook() {
val motionEvent = chain.getArg(0) as MotionEvent override fun beforeHookedMethod(param: MethodHookParam) {
val action = motionEvent.action val motionEvent = param.args[0] as MotionEvent
val action = motionEvent.action
val leftStickX = motionEvent.getAxisValue(MotionEvent.AXIS_X) // 左摇杆的X和Y轴
val leftStickY = motionEvent.getAxisValue(MotionEvent.AXIS_Y) val leftStickX = motionEvent.getAxisValue(MotionEvent.AXIS_X)
val rightStickX = motionEvent.getAxisValue(MotionEvent.AXIS_Z) val leftStickY = motionEvent.getAxisValue(MotionEvent.AXIS_Y)
val rightStickY = motionEvent.getAxisValue(MotionEvent.AXIS_RZ)
val leftTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_LTRIGGER)
val rightTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_RTRIGGER)
val hatX = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X)
val hatY = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y)
joystickEvent( // 右摇杆的X和Y轴
action, val rightStickX = motionEvent.getAxisValue(MotionEvent.AXIS_Z)
leftStickX, val rightStickY = motionEvent.getAxisValue(MotionEvent.AXIS_RZ)
leftStickY,
rightStickX, // 左扳机
rightStickY, val leftTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_LTRIGGER)
leftTrigger,
rightTrigger, // 右扳机
hatX, val rightTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_RTRIGGER)
hatY
) // 十字键
val hatX = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_X)
val hatY = motionEvent.getAxisValue(MotionEvent.AXIS_HAT_Y)
// 处理摇杆和扳机事件
joystickEvent(
action,
leftStickX,
leftStickY,
rightStickX,
rightStickY,
leftTrigger,
rightTrigger,
hatX,
hatY
)
}
} }
) )
val activityClass = classLoader.loadClass("android.app.Activity") val appActivityClass = XposedHelpers.findClass("android.app.Activity", lpparam.classLoader)
hookAllMethods(activityClass, "onStart") { chain -> XposedBridge.hookAllMethods(appActivityClass, "onStart", object : XC_MethodHook() {
Log.d(TAG, "onStart") override fun beforeHookedMethod(param: MethodHookParam) {
val currActivity = chain.thisObject as Activity super.beforeHookedMethod(param)
gameActivity = currActivity Log.d(TAG, "onStart")
if (getConfigError != null) { val currActivity = param.thisObject as Activity
showGetConfigFailed(currActivity) gameActivity = currActivity
} else { if (getConfigError != null) {
initGkmsConfig(currActivity) showGetConfigFailed(currActivity)
}
else {
initGkmsConfig(currActivity)
}
} }
chain.proceed() })
}
hookAllMethods(activityClass, "onResume") { chain -> XposedBridge.hookAllMethods(appActivityClass, "onResume", object : XC_MethodHook() {
Log.d(TAG, "onResume") override fun beforeHookedMethod(param: MethodHookParam) {
val currActivity = chain.thisObject as Activity Log.d(TAG, "onResume")
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() })
}
val unityPlayerClass = classLoader.loadClass("com.unity3d.player.UnityPlayer") val cls = lpparam.classLoader.loadClass("com.unity3d.player.UnityPlayer")
val loadNativeMethod = unityPlayerClass.getDeclaredMethod("loadNative", String::class.java) XposedHelpers.findAndHookMethod(
cls,
"loadNative",
String::class.java,
object : XC_MethodHook() {
@SuppressLint("UnsafeDynamicallyLoadedCode")
override fun afterHookedMethod(param: MethodHookParam) {
super.afterHookedMethod(param)
hook(loadNativeMethod).intercept { chain -> Log.i(TAG, "UnityPlayer.loadNative")
val result = chain.proceed()
onUnityLoadNativeAfterHook() if (alreadyInitialized) {
result return
} }
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 {
@ -243,20 +218,21 @@ class GakumasHookMain : XposedModule() {
while (isActive) { while (isActive) {
val timeTaken = measureTimeMillis { val timeTaken = measureTimeMillis {
val returnValue = pluginCallbackLooper() val returnValue = pluginCallbackLooper() // plugin main thread loop
if (returnValue == 9) { if (returnValue == 9) {
NativeInitProgress.startInit = true NativeInitProgress.startInit = true
} }
if (NativeInitProgress.startInit) { if (NativeInitProgress.startInit) { // if init, update data
NativeInitProgress.pluginInitProgressLooper(NativeInitProgress) NativeInitProgress.pluginInitProgressLooper(NativeInitProgress)
gameActivity?.let { initProgressUI.updateData(it) } gameActivity?.let { initProgressUI.updateData(it) }
} }
if ((gameActivity != null) && (lastFrameStartInit != NativeInitProgress.startInit)) { 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!!)
} }
} }
@ -293,19 +269,24 @@ class GakumasHookMain : XposedModule() {
null null
} }
// 清理本地文件
if (programConfig?.cleanLocalAssets == true) { if (programConfig?.cleanLocalAssets == true) {
FilesChecker.cleanAssets() FilesChecker.cleanAssets()
} }
// 检查 files 版本和 assets 版本并更新
if (programConfig?.checkBuiltInAssets == true) { if (programConfig?.checkBuiltInAssets == true) {
FilesChecker.initAndCheck(activity.filesDir, modulePath) FilesChecker.initAndCheck(activity.filesDir, modulePath)
} }
// 强制导出 assets 文件
if (initConfig?.forceExportResource == true) { if (initConfig?.forceExportResource == true) {
FilesChecker.updateFiles() FilesChecker.updateFiles()
} }
// 使用热更新文件
if ((programConfig?.useRemoteAssets == true) || (programConfig?.useAPIAssets == true)) { if ((programConfig?.useRemoteAssets == true) || (programConfig?.useAPIAssets == true)) {
// val dataUri = intent.data
val dataUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val dataUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra("resource_file", Uri::class.java) intent.getParcelableExtra("resource_file", Uri::class.java)
} else { } else {
@ -316,6 +297,7 @@ class GakumasHookMain : XposedModule() {
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)
} }
@ -323,6 +305,7 @@ class GakumasHookMain : XposedModule() {
else if (programConfig.useAPIAssets) { else if (programConfig.useAPIAssets) {
if (!File(activity.filesDir, localizationFilesDir).exists() && if (!File(activity.filesDir, localizationFilesDir).exists() &&
(initConfig?.forceExportResource == false)) { (initConfig?.forceExportResource == false)) {
// 使用 API 资源不检查内置API 资源无效,且游戏内没有插件数据时,释放内置数据
FilesChecker.initAndCheck(activity.filesDir, modulePath) FilesChecker.initAndCheck(activity.filesDir, modulePath)
} }
} }
@ -461,6 +444,10 @@ class GakumasHookMain : XposedModule() {
} }
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)
@ -481,25 +468,21 @@ class GakumasHookMain : XposedModule() {
@JvmStatic @JvmStatic
external fun loadConfig(configJsonStr: String) external fun loadConfig(configJsonStr: String)
// Toast快速切换内容
private var toast: Toast? = null private var toast: Toast? = null
@JvmStatic @JvmStatic
fun showToast(message: String) { fun showToast(message: String) {
val app = try { val app = AndroidAppHelper.currentApplication()
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())
handler.post { handler.post {
// 取消之前的 Toast
toast?.cancel() toast?.cancel()
// 创建新的 Toast
toast = Toast.makeText(context, message, Toast.LENGTH_SHORT) toast = Toast.makeText(context, message, Toast.LENGTH_SHORT)
// 展示新的 Toast
toast?.show() toast?.show()
} }
} }
@ -511,4 +494,19 @@ class GakumasHookMain : XposedModule() {
@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,11 +48,8 @@ class MainActivity : ComponentActivity(), ConfigUpdateListener, IConfigurableAct
} }
fun gotoPatchActivity() { fun gotoPatchActivity() {
mainUIConfirmStatUpdate( val intent = Intent(this, PatchActivity::class.java)
isShow = true, startActivity(intent)
title = getString(R.string.patcher_unavailable_title),
content = getString(R.string.patcher_unavailable_content)
)
} }
override fun saveConfig() { override fun saveConfig() {

View File

@ -1,16 +1,15 @@
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"
@ -18,6 +17,7 @@ object FilesChecker {
fun initAndCheck(fileDir: File, modulePath: String) { fun initAndCheck(fileDir: File, modulePath: String) {
initDir(fileDir, modulePath) initDir(fileDir, modulePath)
checkFiles() checkFiles()
} }
@ -46,28 +46,31 @@ object FilesChecker {
pluginBasePath.mkdirs() pluginBasePath.mkdirs()
} }
val rootAssetDir = moduleAssetPath(localizationFilesDir).trimEnd('/') + "/" val assets = XModuleResources.createInstance(modulePath, null).assets
ZipFile(modulePath).use { zipFile -> fun forAllAssetFiles(
val entries = zipFile.entries() basePath: String,
while (entries.hasMoreElements()) { action: (String, InputStream?) -> Unit
val entry = entries.nextElement() ) {
val name = entry.name val assetFiles = assets.list(basePath)!!
if (!name.startsWith(rootAssetDir)) continue for (file in assetFiles) {
try {
val relativePath = name.removePrefix(MODULE_ASSETS_PREFIX) assets.open("$basePath/$file")
if (relativePath.isBlank()) continue } catch (e: IOException) {
action("$basePath/$file", null)
val outFile = File(filesDir, relativePath) forAllAssetFiles("$basePath/$file", action)
if (entry.isDirectory) {
outFile.mkdirs()
continue continue
}.use {
action("$basePath/$file", it)
} }
}
outFile.parentFile?.mkdirs() }
zipFile.getInputStream(entry).use { input -> forAllAssetFiles(localizationFilesDir) { path, file ->
outFile.outputStream().use { output -> val outFile = File(filesDir, path)
input.copyTo(output) if (file == null) {
} outFile.mkdirs()
} else {
outFile.outputStream().use { out ->
file.copyTo(out)
} }
} }
} }
@ -76,13 +79,15 @@ object FilesChecker {
} }
fun getPluginVersion(): String { fun getPluginVersion(): String {
val versionAssetPath = moduleAssetPath("$localizationFilesDir/version.txt") val assets = XModuleResources.createInstance(modulePath, null).assets
ZipFile(modulePath).use { zipFile ->
val entry = zipFile.getEntry(versionAssetPath) ?: return "0.0" for (i in assets.list(localizationFilesDir)!!) {
zipFile.getInputStream(entry).use { stream -> if (i.toString() == "version.txt") {
val stream = assets.open("$localizationFilesDir/$i")
return convertToString(stream).trim() return convertToString(stream).trim()
} }
} }
return "0.0"
} }
fun getInstalledVersion(): String { fun getInstalledVersion(): String {
@ -95,25 +100,26 @@ object FilesChecker {
} }
fun convertToString(inputStream: InputStream?): String { fun convertToString(inputStream: InputStream?): String {
if (inputStream == null) return "" val stringBuilder = StringBuilder()
return try { var reader: BufferedReader? = null
BufferedReader(InputStreamReader(inputStream)).use { reader -> try {
buildString { reader = BufferedReader(InputStreamReader(inputStream))
var line: String? var line: String?
while (reader.readLine().also { line = it } != null) { while (reader.readLine().also { line = it } != null) {
append(line) stringBuilder.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 {

View File

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

View File

@ -102,8 +102,6 @@
<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">该模块已迁移到新版 Xposed API暂不支持内嵌修补模式。</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

@ -102,8 +102,6 @@
<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">This module has migrated to the modern Xposed API. Embedded patch mode is temporarily unsupported.</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

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

View File

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

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"
libxposedApi = "101.0.0" xposedApi = "82"
appcompat = "1.7.0" appcompat = "1.7.0"
coil = "2.6.0" coil = "2.6.0"
composeBom = "2024.06.00" composeBom = "2024.06.00"
@ -50,7 +50,7 @@ rikka-hidden-stub = { module = "dev.rikka.hidden:stub", version.ref = "hiddenapi
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" } logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" }
okhttp = { module = "com.squareup.okhttp3:okhttp" } okhttp = { module = "com.squareup.okhttp3:okhttp" }
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" } okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" }
libxposed-api = { module = "io.github.libxposed:api", version.ref = "libxposedApi" } xposed-api = { module = "de.robv.android.xposed:api", version.ref = "xposedApi" }
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,6 +18,7 @@ dependencyResolutionManagement {
google() google()
mavenCentral() mavenCentral()
maven { url "https://api.xposed.info/" }
} }
} }