Treewide: Migrate XPosed API to 101

This commit is contained in:
WeiguangTWK 2026-03-30 17:14:52 +08:00
parent c7af3e41a5
commit 048827feee
8 changed files with 239 additions and 257 deletions

View File

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

View File

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

View File

@ -3,7 +3,7 @@ package io.github.chinosk.gakumas.localify
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.app.AndroidAppHelper import android.app.Application
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
@ -17,34 +17,32 @@ import android.view.MotionEvent
import android.widget.Toast import android.widget.Toast
import com.bytedance.shadowhook.ShadowHook import com.bytedance.shadowhook.ShadowHook
import com.bytedance.shadowhook.ShadowHook.ConfigBuilder import com.bytedance.shadowhook.ShadowHook.ConfigBuilder
import de.robv.android.xposed.IXposedHookLoadPackage import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers
import de.robv.android.xposed.callbacks.XC_LoadPackage
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.localizationFilesDir
import io.github.chinosk.gakumas.localify.mainUtils.json
import io.github.chinosk.gakumas.localify.models.GakumasConfig import io.github.chinosk.gakumas.localify.models.GakumasConfig
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
import io.github.chinosk.gakumas.localify.models.ProgramConfig
import io.github.chinosk.gakumas.localify.ui.game_attach.InitProgressUI
import io.github.libxposed.api.XposedInterface
import io.github.libxposed.api.XposedModule
import io.github.libxposed.api.XposedModuleInterface
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.lang.reflect.Method
import java.util.Locale import java.util.Locale
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
import io.github.chinosk.gakumas.localify.hookUtils.FileHotUpdater
import io.github.chinosk.gakumas.localify.hookUtils.FilesChecker.localizationFilesDir
import io.github.chinosk.gakumas.localify.mainUtils.json
import io.github.chinosk.gakumas.localify.models.NativeInitProgress
import io.github.chinosk.gakumas.localify.models.ProgramConfig
import io.github.chinosk.gakumas.localify.ui.game_attach.InitProgressUI
val TAG = "GakumasLocalify" val TAG = "GakumasLocalify"
class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit { class GakumasHookMain : XposedModule() {
private lateinit var modulePath: String private var modulePath: String = ""
private var nativeLibLoadSuccess: Boolean private var nativeLibLoadSuccess: Boolean = false
private var alreadyInitialized = false private var alreadyInitialized = false
private val targetPackageName = "com.bandainamcoent.idolmaster_gakuen" private val targetPackageName = "com.bandainamcoent.idolmaster_gakuen"
private val nativeLibName = "MarryKotone" private val nativeLibName = "MarryKotone"
@ -55,160 +53,187 @@ 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轴 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) val rightStickX = motionEvent.getAxisValue(MotionEvent.AXIS_Z)
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)
// 右摇杆的X和Y轴 joystickEvent(
val rightStickX = motionEvent.getAxisValue(MotionEvent.AXIS_Z) action,
val rightStickY = motionEvent.getAxisValue(MotionEvent.AXIS_RZ) leftStickX,
leftStickY,
// 左扳机 rightStickX,
val leftTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_LTRIGGER) rightStickY,
leftTrigger,
// 右扳机 rightTrigger,
val rightTrigger = motionEvent.getAxisValue(MotionEvent.AXIS_RTRIGGER) hatX,
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 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 {
@ -218,21 +243,20 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
while (isActive) { while (isActive) {
val timeTaken = measureTimeMillis { val timeTaken = measureTimeMillis {
val returnValue = pluginCallbackLooper() // plugin main thread loop val returnValue = pluginCallbackLooper()
if (returnValue == 9) { if (returnValue == 9) {
NativeInitProgress.startInit = true NativeInitProgress.startInit = true
} }
if (NativeInitProgress.startInit) { // if init, update data if (NativeInitProgress.startInit) {
NativeInitProgress.pluginInitProgressLooper(NativeInitProgress) NativeInitProgress.pluginInitProgressLooper(NativeInitProgress)
gameActivity?.let { initProgressUI.updateData(it) } gameActivity?.let { initProgressUI.updateData(it) }
} }
if ((gameActivity != null) && (lastFrameStartInit != NativeInitProgress.startInit)) { // change status if ((gameActivity != null) && (lastFrameStartInit != NativeInitProgress.startInit)) {
if (NativeInitProgress.startInit) { if (NativeInitProgress.startInit) {
initProgressUI.createView(gameActivity!!) initProgressUI.createView(gameActivity!!)
} } else {
else {
initProgressUI.finishLoad(gameActivity!!) initProgressUI.finishLoad(gameActivity!!)
} }
} }
@ -269,24 +293,19 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
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 {
@ -297,7 +316,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
if (dataUri != null) { if (dataUri != null) {
if (!externalFilesChecked) { if (!externalFilesChecked) {
externalFilesChecked = true externalFilesChecked = true
// Log.d(TAG, "dataUri: $dataUri")
FileHotUpdater.updateFilesFromZip(activity, dataUri, activity.filesDir, FileHotUpdater.updateFilesFromZip(activity, dataUri, activity.filesDir,
programConfig.delRemoteAfterUpdate) programConfig.delRemoteAfterUpdate)
} }
@ -305,7 +323,6 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
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)
} }
} }
@ -444,10 +461,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)
@ -468,21 +481,25 @@ class GakumasHookMain : IXposedHookLoadPackage, IXposedHookZygoteInit {
@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 = 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())
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()
} }
} }
@ -494,19 +511,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

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

View File

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

View File

@ -0,0 +1,3 @@
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"
xposedApi = "82" libxposedApi = "101.0.0"
appcompat = "1.7.0" appcompat = "1.7.0"
coil = "2.6.0" coil = "2.6.0"
composeBom = "2024.06.00" composeBom = "2024.06.00"
@ -50,7 +50,7 @@ rikka-hidden-stub = { module = "dev.rikka.hidden:stub", version.ref = "hiddenapi
logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" } logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor" }
okhttp = { module = "com.squareup.okhttp3:okhttp" } okhttp = { module = "com.squareup.okhttp3:okhttp" }
okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" } okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttpBom" }
xposed-api = { module = "de.robv.android.xposed:api", version.ref = "xposedApi" } libxposed-api = { module = "io.github.libxposed:api", version.ref = "libxposedApi" }
coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" } coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" } coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
material = { module = "com.google.android.material:material", version.ref = "material" } material = { module = "com.google.android.material:material", version.ref = "material" }

View File

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