treewide: Upgrade API level to 101 and change name to GKMSPatch
This commit is contained in:
parent
41872d8261
commit
98cd89f9d0
|
|
@ -18,3 +18,7 @@ libxposed
|
|||
.cxx
|
||||
/out
|
||||
/.idea
|
||||
|
||||
# JVM/Android local build artifacts
|
||||
**/bin/
|
||||
**/release/
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ val (coreCommitCount, coreLatestTag) = FileRepositoryBuilder().setGitDir(rootPro
|
|||
}.getOrNull() ?: (1145 to "1.0")
|
||||
|
||||
// sync from https://github.com/JingMartix/LSPosed/blob/master/build.gradle.kts
|
||||
val defaultManagerPackageName by extra("org.lsposed.npatch")
|
||||
val apiCode by extra(100)
|
||||
val defaultManagerPackageName by extra("org.gkmspatch")
|
||||
val apiCode by extra(101)
|
||||
val verCode by extra(commitCount)
|
||||
val verName by extra("0.7.4")
|
||||
val coreVerCode by extra(coreCommitCount)
|
||||
|
|
@ -259,4 +259,4 @@ project(":core") {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ afterEvaluate {
|
|||
dependsOn("assemble$variantCapped")
|
||||
from(variant.outputs.map { it.outputFile })
|
||||
into("${rootProject.projectDir}/out/$variantLowered")
|
||||
rename(".*.apk", "NPatch-v$verName-$verCode-$variantLowered.apk")
|
||||
rename(".*.apk", "GKMSPatch-v$verName-$verCode-$variantLowered.apk")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -127,4 +127,4 @@ dependencies {
|
|||
debugImplementation(npatch.androidx.compose.ui.tooling)
|
||||
debugImplementation(npatch.androidx.customview)
|
||||
debugImplementation(npatch.androidx.customview.poolingcontainer)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,9 +93,9 @@
|
|||
|
||||
<provider
|
||||
android:name=".manager.ConfigProvider"
|
||||
android:authorities="org.lsposed.npatch.manager.provider.config"
|
||||
android:authorities="org.gkmspatch.manager.provider.config"
|
||||
android:exported="true"
|
||||
android:enabled="true" />
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
|
|
|||
|
|
@ -29,8 +29,11 @@ import org.lsposed.npatch.lspApp
|
|||
import org.lsposed.npatch.share.Constants
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.text.Collator
|
||||
import java.util.*
|
||||
import java.util.Properties
|
||||
import java.util.zip.ZipFile
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
|
@ -42,9 +45,18 @@ object NPackageManager {
|
|||
const val STATUS_USER_CANCELLED = -2
|
||||
|
||||
@Parcelize
|
||||
class AppInfo(val app: ApplicationInfo, val label: String) : Parcelable {
|
||||
class AppInfo(
|
||||
val app: ApplicationInfo,
|
||||
val label: String,
|
||||
val xposedApi: Int? = null,
|
||||
val xposedMinApi: Int? = null,
|
||||
val xposedTargetApi: Int? = null,
|
||||
val xposedStaticScope: Boolean = false,
|
||||
val xposedDescription: String = "",
|
||||
val xposedScope: List<String> = emptyList(),
|
||||
) : Parcelable {
|
||||
val isXposedModule: Boolean
|
||||
get() = app.metaData?.get("xposedminversion") != null
|
||||
get() = xposedApi != null
|
||||
}
|
||||
|
||||
var appList by mutableStateOf(listOf<AppInfo>())
|
||||
|
|
@ -76,13 +88,29 @@ object NPackageManager {
|
|||
|
||||
applicationList.forEach {
|
||||
val label = pm.getApplicationLabel(it)
|
||||
collection.add(AppInfo(it, label.toString()))
|
||||
val moduleMeta = resolveModuleMeta(pm, it)
|
||||
collection.add(
|
||||
AppInfo(
|
||||
app = it,
|
||||
label = label.toString(),
|
||||
xposedApi = moduleMeta?.api,
|
||||
xposedMinApi = moduleMeta?.minApi,
|
||||
xposedTargetApi = moduleMeta?.targetApi,
|
||||
xposedStaticScope = moduleMeta?.staticScope ?: false,
|
||||
xposedDescription = moduleMeta?.description.orEmpty(),
|
||||
xposedScope = moduleMeta?.scope.orEmpty(),
|
||||
)
|
||||
)
|
||||
appIcon[it.packageName] = iconLoader.loadIcon(it).asImageBitmap()
|
||||
}
|
||||
|
||||
collection.sortWith(compareBy(Collator.getInstance(Locale.getDefault()), AppInfo::label))
|
||||
val modules = buildMap {
|
||||
collection.forEach { if (it.isXposedModule) put(it.app.packageName, it.app.sourceDir) }
|
||||
collection.forEach {
|
||||
if (it.isXposedModule && !it.app.sourceDir.isNullOrBlank()) {
|
||||
put(it.app.packageName, it.app.sourceDir)
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigManager.updateModules(modules)
|
||||
appList = collection
|
||||
|
|
@ -203,7 +231,17 @@ object NPackageManager {
|
|||
}
|
||||
if (primary == null) primary = appInfo
|
||||
val label = lspApp.packageManager.getApplicationLabel(appInfo).toString()
|
||||
AppInfo(appInfo, label)
|
||||
val moduleMeta = resolveModuleMeta(lspApp.packageManager, appInfo)
|
||||
AppInfo(
|
||||
app = appInfo,
|
||||
label = label,
|
||||
xposedApi = moduleMeta?.api,
|
||||
xposedMinApi = moduleMeta?.minApi,
|
||||
xposedTargetApi = moduleMeta?.targetApi,
|
||||
xposedStaticScope = moduleMeta?.staticScope ?: false,
|
||||
xposedDescription = moduleMeta?.description.orEmpty(),
|
||||
xposedScope = moduleMeta?.scope.orEmpty(),
|
||||
)
|
||||
}
|
||||
// TODO: Check selected apks are from the same app
|
||||
primary?.splitSourceDirs = splits.toTypedArray()
|
||||
|
|
@ -255,4 +293,74 @@ object NPackageManager {
|
|||
ris[0].activityInfo.name
|
||||
)
|
||||
}
|
||||
|
||||
private data class ModuleMeta(
|
||||
val api: Int?,
|
||||
val minApi: Int?,
|
||||
val targetApi: Int?,
|
||||
val staticScope: Boolean,
|
||||
val description: String,
|
||||
val scope: List<String>,
|
||||
)
|
||||
|
||||
private fun resolveModuleMeta(pm: PackageManager, appInfo: ApplicationInfo): ModuleMeta? {
|
||||
val legacyApi = readLegacyApiFromMeta(appInfo)
|
||||
val legacyDescription = appInfo.metaData?.getString("xposeddescription").orEmpty()
|
||||
val sourceDir = appInfo.sourceDir ?: return if (legacyApi != null) {
|
||||
ModuleMeta(legacyApi, legacyApi, legacyApi, false, legacyDescription, emptyList())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return runCatching {
|
||||
ZipFile(sourceDir).use { apk ->
|
||||
val modernEntry = apk.getEntry("META-INF/xposed/java_init.list")
|
||||
val legacyEntry = apk.getEntry("assets/xposed_init")
|
||||
if (modernEntry == null && legacyEntry == null && legacyApi == null) {
|
||||
null
|
||||
} else if (modernEntry != null) {
|
||||
val prop = Properties()
|
||||
apk.getEntry("META-INF/xposed/module.prop")?.let { propEntry ->
|
||||
apk.getInputStream(propEntry).use { prop.load(it) }
|
||||
}
|
||||
val minApi = extractIntPart(prop.getProperty("minApiVersion"))
|
||||
val targetApi = extractIntPart(prop.getProperty("targetApiVersion"))
|
||||
val api = targetApi ?: minApi ?: 100
|
||||
val staticScope = prop.getProperty("staticScope") == "true"
|
||||
|
||||
val scope =
|
||||
apk.getEntry("META-INF/xposed/scope.list")?.let { scopeEntry ->
|
||||
apk.getInputStream(scopeEntry).use { input ->
|
||||
InputStreamReader(input).readLines().map { it.trim() }
|
||||
.filter { it.isNotEmpty() && !it.startsWith("#") }
|
||||
}
|
||||
} ?: emptyList()
|
||||
|
||||
val description = appInfo.loadDescription(pm)?.toString().orEmpty()
|
||||
ModuleMeta(api, minApi, targetApi, staticScope, description, scope)
|
||||
} else {
|
||||
ModuleMeta(legacyApi ?: 0, legacyApi, legacyApi, false, legacyDescription, emptyList())
|
||||
}
|
||||
}
|
||||
}.getOrElse {
|
||||
if (legacyApi != null) {
|
||||
ModuleMeta(legacyApi, legacyApi, legacyApi, false, legacyDescription, emptyList())
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readLegacyApiFromMeta(appInfo: ApplicationInfo): Int? {
|
||||
val raw = appInfo.metaData?.get("xposedminversion") ?: return null
|
||||
return when (raw) {
|
||||
is Int -> raw
|
||||
is String -> extractIntPart(raw)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractIntPart(value: String?): Int? {
|
||||
if (value.isNullOrBlank()) return null
|
||||
return value.takeWhile { it.isDigit() }.takeIf { it.isNotEmpty() }?.toIntOrNull()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.os.IBinder
|
|||
import android.os.IInterface
|
||||
import android.os.Process
|
||||
import android.os.SystemProperties
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
|
|
@ -16,6 +17,7 @@ import rikka.shizuku.ShizukuBinderWrapper
|
|||
import rikka.shizuku.SystemServiceHelper
|
||||
|
||||
object ShizukuApi {
|
||||
private const val TAG = "ShizukuApi"
|
||||
|
||||
private fun IBinder.wrap() = ShizukuBinderWrapper(this)
|
||||
private fun IInterface.asShizukuBinder() = this.asBinder().wrap()
|
||||
|
|
@ -82,10 +84,79 @@ object ShizukuApi {
|
|||
}
|
||||
|
||||
fun performDexOptMode(packageName: String): Boolean {
|
||||
return iPackageManager.performDexOptMode(
|
||||
packageName,
|
||||
SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false),
|
||||
"verify", true, true, null
|
||||
)
|
||||
val checkProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)
|
||||
val compilerFilter = SystemProperties.get("pm.dexopt.install", "speed-profile")
|
||||
|
||||
// Try framework method first, but do it reflectively so signature changes
|
||||
// across Android versions won't crash manager process.
|
||||
runCatching {
|
||||
val methods = iPackageManager.javaClass.methods
|
||||
.filter { it.name == "performDexOptMode" }
|
||||
for (method in methods) {
|
||||
val args = buildDexOptArgs(method.parameterTypes, packageName, checkProfiles, compilerFilter)
|
||||
if (args == null) continue
|
||||
val ret = method.invoke(iPackageManager, *args)
|
||||
if (ret is Boolean) {
|
||||
Log.i(TAG, "performDexOptMode via IPackageManager#${method.name}${method.parameterTypes.contentToString()} => $ret")
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
Log.w(TAG, "performDexOptMode by reflection failed: ${it.message}", it)
|
||||
}
|
||||
|
||||
// Fallback for newer systems where hidden API signature changed/removed.
|
||||
return runCmdCompile(packageName)
|
||||
}
|
||||
|
||||
private fun buildDexOptArgs(
|
||||
types: Array<Class<*>>,
|
||||
packageName: String,
|
||||
checkProfiles: Boolean,
|
||||
compilerFilter: String
|
||||
): Array<Any?>? {
|
||||
var boolIdx = 0
|
||||
var stringIdx = 0
|
||||
val args = arrayOfNulls<Any>(types.size)
|
||||
for (i in types.indices) {
|
||||
val t = types[i]
|
||||
args[i] = when {
|
||||
t == String::class.java -> {
|
||||
when (stringIdx++) {
|
||||
0 -> packageName
|
||||
1 -> compilerFilter
|
||||
else -> null // splitName/optional string
|
||||
}
|
||||
}
|
||||
t == Boolean::class.javaPrimitiveType || t == java.lang.Boolean::class.java -> {
|
||||
when (boolIdx++) {
|
||||
0 -> checkProfiles
|
||||
1 -> true // force
|
||||
2 -> true // bootComplete
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
t == Int::class.javaPrimitiveType || t == java.lang.Integer::class.java -> 0
|
||||
t == Long::class.javaPrimitiveType || t == java.lang.Long::class.java -> 0L
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
// At minimum we expect first arg is packageName for compatibility.
|
||||
return if (types.isNotEmpty() && types[0] == String::class.java) args else null
|
||||
}
|
||||
|
||||
private fun runCmdCompile(packageName: String): Boolean {
|
||||
return runCatching {
|
||||
val process = Runtime.getRuntime().exec(arrayOf("sh", "-c", "cmd package compile -m speed-profile -f $packageName"))
|
||||
val output = process.inputStream.bufferedReader().use { it.readText() }
|
||||
val err = process.errorStream.bufferedReader().use { it.readText() }
|
||||
val code = process.waitFor()
|
||||
val ok = code == 0 && (output.contains("Success", ignoreCase = true) || err.isBlank())
|
||||
Log.i(TAG, "dexopt fallback exit=$code, out=$output, err=$err")
|
||||
ok
|
||||
}.getOrElse {
|
||||
Log.e(TAG, "dexopt fallback failed: ${it.message}", it)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
|||
import android.content.SharedPreferences
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -17,6 +18,10 @@ import java.io.File
|
|||
lateinit var lspApp: LSPApplication
|
||||
|
||||
class LSPApplication : Application() {
|
||||
companion object {
|
||||
private const val TAG = "LSPApplication"
|
||||
private const val OFFICIAL_MANAGER_PACKAGE = "org.lsposed.npatch"
|
||||
}
|
||||
|
||||
lateinit var prefs: SharedPreferences
|
||||
lateinit var tmpApkDir: File
|
||||
|
|
@ -46,6 +51,10 @@ class LSPApplication : Application() {
|
|||
}
|
||||
|
||||
private fun verifySignature() {
|
||||
// Keep upstream anti-tamper behavior for official package only.
|
||||
// Forked/rebranded packages should not be killed silently at launch.
|
||||
if (packageName != OFFICIAL_MANAGER_PACKAGE) return
|
||||
|
||||
try {
|
||||
val flags = PackageManager.GET_SIGNING_CERTIFICATES
|
||||
val packageInfo = packageManager.getPackageInfo(packageName, flags)
|
||||
|
|
@ -56,12 +65,15 @@ class LSPApplication : Application() {
|
|||
val currentHash = signatures[0].hashCode()
|
||||
val targetHash = 0x0293FA43
|
||||
if (currentHash != targetHash) {
|
||||
Log.e(TAG, "Signature mismatch, killing process")
|
||||
killApp()
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "No signatures found, killing process")
|
||||
killApp()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Signature verification failed, killing process", e)
|
||||
killApp()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,10 +58,11 @@ object Patcher {
|
|||
}
|
||||
lspApp.targetApkFiles?.clear()
|
||||
val apkFileList = arrayListOf<File>()
|
||||
val cacheRoot = lspApp.externalCacheDir ?: lspApp.cacheDir
|
||||
lspApp.tmpApkDir.walk()
|
||||
.filter { it.isFile && it.name.endsWith(Constants.PATCH_FILE_SUFFIX) }
|
||||
.forEach { tempApkFile ->
|
||||
val cachedApkFile = File(lspApp.externalCacheDir, tempApkFile.name)
|
||||
val cachedApkFile = File(cacheRoot, tempApkFile.name)
|
||||
if (tempApkFile.renameTo(cachedApkFile).not()) {
|
||||
tempApkFile.copyTo(cachedApkFile, overwrite = true)
|
||||
tempApkFile.delete()
|
||||
|
|
|
|||
|
|
@ -80,11 +80,25 @@ object ConfigManager {
|
|||
Log.i(TAG, "Module apk path updated: ${it.pkgName}")
|
||||
}
|
||||
loadedModules.getOrPut(it) {
|
||||
val appInfo = try {
|
||||
lspApp.packageManager.getApplicationInfo(it.pkgName, PackageManager.GET_META_DATA)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.w(TAG, "Cannot resolve applicationInfo for module: ${it.pkgName}", e)
|
||||
null
|
||||
}
|
||||
org.lsposed.lspd.models.Module().apply {
|
||||
packageName = it.pkgName
|
||||
apkPath = it.apkPath
|
||||
applicationInfo = appInfo
|
||||
appId = appInfo?.uid ?: 0
|
||||
file = ModuleLoader.loadModule(it.apkPath)
|
||||
}
|
||||
}.takeIf { loaded ->
|
||||
loaded.file != null
|
||||
} ?: run {
|
||||
Log.w(TAG, "Module has no valid entry list: ${it.pkgName}")
|
||||
loadedModules.remove(it)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import org.lsposed.npatch.config.ConfigManager
|
|||
class ConfigProvider : ContentProvider() {
|
||||
|
||||
companion object {
|
||||
const val AUTHORITY = "org.lsposed.npatch.manager.provider.config"
|
||||
const val AUTHORITY = "org.gkmspatch.manager.provider.config"
|
||||
const val TAG = "ConfigProvider"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,13 +22,18 @@ object ManagerService : ILSPApplicationService.Stub() {
|
|||
val app = lspApp.packageManager.getNameForUid(Binder.getCallingUid())
|
||||
val list = app?.let {
|
||||
runBlocking { ConfigManager.getModuleFilesForApp(it) }
|
||||
}.orEmpty()
|
||||
}.orEmpty().filter { it.file?.legacy == true }
|
||||
Log.d(TAG, "$app calls getLegacyModulesList: $list")
|
||||
return list
|
||||
}
|
||||
|
||||
override fun getModulesList(): List<Module> {
|
||||
return emptyList()
|
||||
val app = lspApp.packageManager.getNameForUid(Binder.getCallingUid())
|
||||
val list = app?.let {
|
||||
runBlocking { ConfigManager.getModuleFilesForApp(it) }
|
||||
}.orEmpty().filter { it.file?.legacy == false }
|
||||
Log.d(TAG, "$app calls getModulesList: $list")
|
||||
return list
|
||||
}
|
||||
|
||||
override fun getPrefsPath(packageName: String): String {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,20 @@ fun ModuleManageBody() {
|
|||
fontFamily = FontFamily.Serif,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
val minApiText = it.second.minApi?.toString() ?: "-"
|
||||
val targetApiText = it.second.targetApi?.toString() ?: "-"
|
||||
Text(
|
||||
text = "min/target $minApiText / $targetApiText",
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontFamily = FontFamily.Serif,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
Text(
|
||||
text = "staticScope ${if (it.second.staticScope) "true" else "false"}",
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontFamily = FontFamily.Serif,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,17 +23,22 @@ class ModuleManageViewModel : ViewModel() {
|
|||
|
||||
class XposedInfo(
|
||||
val api: Int,
|
||||
val minApi: Int?,
|
||||
val targetApi: Int?,
|
||||
val staticScope: Boolean,
|
||||
val description: String,
|
||||
val scope: List<String>
|
||||
)
|
||||
|
||||
val appList: List<Pair<NPackageManager.AppInfo, XposedInfo>> by derivedStateOf {
|
||||
NPackageManager.appList.mapNotNull { appInfo ->
|
||||
val metaData = appInfo.app.metaData ?: return@mapNotNull null
|
||||
appInfo to XposedInfo(
|
||||
metaData.getInt("xposedminversion", -1).also { if (it == -1) return@mapNotNull null },
|
||||
metaData.getString("xposeddescription") ?: "",
|
||||
emptyList() // TODO: scope
|
||||
appInfo.xposedApi ?: return@mapNotNull null,
|
||||
appInfo.xposedMinApi,
|
||||
appInfo.xposedTargetApi,
|
||||
appInfo.xposedStaticScope,
|
||||
appInfo.xposedDescription,
|
||||
appInfo.xposedScope
|
||||
)
|
||||
}.also {
|
||||
Log.d(TAG, "Loaded ${it.size} Xposed modules")
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
<string name="home_system_abi">系统架构</string>
|
||||
<string name="home_info_copied">已复制到剪贴板</string>
|
||||
<string name="home_support">支持</string>
|
||||
<string name="home_description">NPatch 是一款免费且迭代于 LSPatch 的,基于 LSPosed 核心的免 Root 的 Xposed 框架。</string>
|
||||
<string name="home_description">GKMSPatch 是一款基于 NPatch 且迭代于 LSPatch 的,基于 LSPosed 核心的免 Root 的 Xposed 框架。仅针对 GKMS-localify 的 API 101 版本进行适配优化。</string>
|
||||
<string name="home_view_source_code"><![CDATA[查看源代码 %1$s<br/>加入我们的 %2$s 频道]]></string>
|
||||
<!-- Manage Screen -->
|
||||
<string name="screen_manage">管理</string>
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
<string name="home_system_abi">系統架构</string>
|
||||
<string name="home_info_copied">已複製到剪贴板</string>
|
||||
<string name="home_support">支持</string>
|
||||
<string name="home_description">NPatch 是一款免费的迭代于 LSPatch 的,基于 LSPosed 核心的免 Root 的 Xposed 框架。</string>
|
||||
<string name="home_description">GKMSPatch 是一款基於 NPatch 且迭代於 LSPatch 的,基於 LSPosed 核心的免 Root 的 Xposed 框架。僅針對 GKMS-localify 的 API 101 版本進行適配優化。</string>
|
||||
<string name="home_view_source_code">查看源代码 %1$s<br/>加入我们的 %2$s 频道</string>
|
||||
<!-- Manage Screen -->
|
||||
<string name="screen_manage">管理</string>
|
||||
|
|
@ -91,4 +91,4 @@
|
|||
<string name="enable_install_activity_title">启用 install activity</string>
|
||||
<string name="patch_inject_dex">注入載入器 Dex</string>
|
||||
<string name="patch_inject_dex_desc">对那些需要孤立服务进程的應用程式程序,譬如说浏览器的渲染引擎,请勾选此选项以确保他们正常运行</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
<string name="home_system_abi">系統架構</string>
|
||||
<string name="home_info_copied">已複製到剪貼簿</string>
|
||||
<string name="home_support">支援</string>
|
||||
<string name="home_description">NPatch 是一款免費且迭代自 LSPatch 的,基於 LSPosed 核心的免 Root 的 Xposed 框架。</string>
|
||||
<string name="home_description">GKMSPatch 是一款基於 NPatch 且迭代於 LSPatch 的,基於 LSPosed 核心的免 Root 的 Xposed 框架。僅針對 GKMS-localify 的 API 101 版本進行適配優化。</string>
|
||||
<string name="home_view_source_code"><![CDATA[查看原始碼 %1$s<br/>加入我們的 %2$s 頻道]]></string>
|
||||
<!-- Manage Screen -->
|
||||
<string name="screen_manage">管理</string>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<!-- Home Screen -->
|
||||
<string name="home_shizuku_warning">Some functions unavailable</string>
|
||||
<string name="home_api_version">API Version</string>
|
||||
<string name="home_npatch_version">NPatch Version</string>
|
||||
<string name="home_npatch_version">GKMSPatch Version</string>
|
||||
<string name="home_framework_version">Framework Version</string>
|
||||
<string name="home_system_version">System Version</string>
|
||||
<string name="home_device">Device</string>
|
||||
|
|
@ -27,7 +27,7 @@
|
|||
<string name="home_system_abi">System ABI</string>
|
||||
<string name="home_info_copied">Copied to clipboard</string>
|
||||
<string name="home_support">Support</string>
|
||||
<string name="home_description">NPatch is a free non-root Xposed framework based on LSPosed core.</string>
|
||||
<string name="home_description">GKMSPatch is a non-root Xposed framework based on NPatch and iterated from LSPatch, built on LSPosed core. It is only adapted and optimized for the API 101 version of GKMS-localify.</string>
|
||||
<string name="home_view_source_code"><![CDATA[View source code at %1$s<br/>Join our %2$s channel]]></string>
|
||||
|
||||
<!-- Manage Screen -->
|
||||
|
|
@ -57,9 +57,9 @@
|
|||
<string name="patch_from_applist">Select an installed app</string>
|
||||
<string name="patch_mode">Patch Mode</string>
|
||||
<string name="patch_local">Local</string>
|
||||
<string name="patch_local_desc">Patches apps without embedding modules.\nThe Xposed scope can be dynamically changed without needing to re-patch.\nThis mode can only be used locally with the NPatch Manager.</string>
|
||||
<string name="patch_local_desc">Patches apps without embedding modules.\nThe Xposed scope can be dynamically changed without needing to re-patch.\nThis mode can only be used locally with the GKMSPatch Manager.</string>
|
||||
<string name="patch_integrated">Integrated</string>
|
||||
<string name="patch_integrated_desc">Patches apps with built-in modules.\nThe patched app can run independently without the Manager, but it cannot dynamically manage configurations.\nThis mode is suitable for apps that need to run on devices without the NPatch Manager installed.</string>
|
||||
<string name="patch_integrated_desc">Patches apps with built-in modules.\nThe patched app can run independently without the Manager, but it cannot dynamically manage configurations.\nThis mode is suitable for apps that need to run on devices without the GKMSPatch Manager installed.</string>
|
||||
<string name="patch_embed_modules">Embed modules</string>
|
||||
<string name="patch_debuggable">Debuggable</string>
|
||||
<string name="patch_sigbypass">Signature bypass</string>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name" translatable="false">NPatch</string>
|
||||
<string name="app_name" translatable="false">GKMSPatch</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ android {
|
|||
androidComponents.onVariants { variant ->
|
||||
val variantCapped = variant.name.replaceFirstChar { it.uppercase() }
|
||||
|
||||
tasks.matching { it.name == "create${variantCapped}ApkListingFileRedirect" }
|
||||
.configureEach {
|
||||
dependsOn(":meta-loader:package$variantCapped")
|
||||
}
|
||||
|
||||
val copyDexTask = tasks.register<Copy>("copyDex$variantCapped") {
|
||||
dependsOn("assemble$variantCapped")
|
||||
from(layout.buildDirectory.file("intermediates/dex/${variant.name}/mergeDex$variantCapped/classes.dex"))
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import android.content.SharedPreferences;
|
|||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.res.CompatibilityInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Process;
|
||||
import android.system.Os;
|
||||
|
|
@ -46,6 +48,7 @@ import java.util.Map;
|
|||
import java.util.function.BiConsumer;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedInit;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import hidden.HiddenApiBridge;
|
||||
|
||||
|
|
@ -65,6 +68,7 @@ public class LSPApplication {
|
|||
private static ActivityThread activityThread;
|
||||
private static LoadedApk stubLoadedApk;
|
||||
private static LoadedApk appLoadedApk;
|
||||
private static boolean modernModulesInitialized = false;
|
||||
|
||||
private static PatchConfig config;
|
||||
|
||||
|
|
@ -99,10 +103,15 @@ public class LSPApplication {
|
|||
if (config.useManager) {
|
||||
try {
|
||||
service = new RemoteApplicationService(context);
|
||||
List<Module> m = service.getLegacyModulesList();
|
||||
List<Module> m = new ArrayList<>();
|
||||
m.addAll(service.getLegacyModulesList());
|
||||
m.addAll(service.getModulesList());
|
||||
JSONArray moduleArr = new JSONArray();
|
||||
if (m != null) {
|
||||
for (Module module : m) {
|
||||
if (module == null || module.apkPath == null || module.packageName == null) {
|
||||
continue;
|
||||
}
|
||||
JSONObject moduleObj = new JSONObject();
|
||||
moduleObj.put("path", module.apkPath);
|
||||
moduleObj.put("packageName", module.packageName);
|
||||
|
|
@ -127,10 +136,12 @@ public class LSPApplication {
|
|||
service = new NeoLocalApplicationService(context);
|
||||
}
|
||||
}
|
||||
logServiceModuleStats(service);
|
||||
|
||||
disableProfile(context);
|
||||
Startup.initXposed(false, ActivityThread.currentProcessName(), context.getApplicationInfo().dataDir, service);
|
||||
Startup.bootstrapXposed();
|
||||
ensureModernModulesInitialized(service);
|
||||
|
||||
// WARN: Since it uses `XResource`, the following class should not be initialized
|
||||
// before forkPostCommon is invoke. Otherwise, you will get failure of XResources
|
||||
|
|
@ -138,9 +149,28 @@ public class LSPApplication {
|
|||
if (config.outputLog) {
|
||||
XposedBridge.setLogPrinter(new XposedLogPrinter(0, "NPatch"));
|
||||
}
|
||||
Log.i(TAG, "Load modules");
|
||||
LSPLoader.initModules(appLoadedApk);
|
||||
Log.i(TAG, "Modules initialized");
|
||||
try {
|
||||
Log.i(TAG, "Load modules");
|
||||
LSPLoader.initModules(appLoadedApk);
|
||||
boolean modernDispatched = LSPLoader.dispatchModernCallbacksNow(appLoadedApk);
|
||||
if (!modernDispatched) {
|
||||
Log.w(TAG, "Modern callbacks not ready in sync stage, fallback to async retries");
|
||||
LSPLoader.dispatchModernCallbacksAsync(appLoadedApk);
|
||||
}
|
||||
Log.i(TAG, "Modules initialized");
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "Failed to initialize modules", t);
|
||||
Log.i(TAG, "Fallback: schedule module init on main looper");
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(() -> {
|
||||
try {
|
||||
LSPLoader.initModules(appLoadedApk);
|
||||
LSPLoader.dispatchModernCallbacksAsync(appLoadedApk);
|
||||
} catch (Throwable tt) {
|
||||
Log.e(TAG, "Fallback async init failed", tt);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
switchAllClassLoader();
|
||||
SigBypass.doSigBypass(context, config.sigBypassLevel);
|
||||
|
|
@ -153,6 +183,44 @@ public class LSPApplication {
|
|||
Log.i(TAG, "NPatch bootstrap completed");
|
||||
}
|
||||
|
||||
private static void ensureModernModulesInitialized(ILSPApplicationService service) {
|
||||
if (modernModulesInitialized) return;
|
||||
try {
|
||||
List<Module> requested = service.getModulesList();
|
||||
List<String> requestedPkgs = new ArrayList<>();
|
||||
if (requested != null) {
|
||||
for (Module module : requested) {
|
||||
if (module != null && module.packageName != null) {
|
||||
requestedPkgs.add(module.packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
XposedInit.loadModules(activityThread);
|
||||
modernModulesInitialized = true;
|
||||
List<String> failedPkgs = new ArrayList<>();
|
||||
for (String pkg : requestedPkgs) {
|
||||
if (!XposedInit.getLoadedModules().containsKey(pkg)) {
|
||||
failedPkgs.add(pkg);
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "Modern modules initialized explicitly, requested=" + requestedPkgs + ", failed=" + failedPkgs);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "Failed to initialize modern modules explicitly", t);
|
||||
}
|
||||
}
|
||||
|
||||
private static void logServiceModuleStats(ILSPApplicationService service) {
|
||||
try {
|
||||
List<Module> legacy = service.getLegacyModulesList();
|
||||
List<Module> modern = service.getModulesList();
|
||||
int legacyCount = legacy == null ? 0 : legacy.size();
|
||||
int modernCount = modern == null ? 0 : modern.size();
|
||||
Log.i(TAG, "Service module stats: legacy=" + legacyCount + ", modern=" + modernCount);
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "Failed to read service module stats", t);
|
||||
}
|
||||
}
|
||||
|
||||
private static Context createLoadedApkWithContext() {
|
||||
try {
|
||||
var timeStart = System.currentTimeMillis();
|
||||
|
|
@ -302,8 +370,27 @@ public class LSPApplication {
|
|||
for (Field field : fields) {
|
||||
if (field.getType() == ClassLoader.class) {
|
||||
var obj = XposedHelpers.getObjectField(appLoadedApk, field.getName());
|
||||
if (obj == null) continue;
|
||||
// Android 14+ may throw when addNative() sees a non-PathClassLoader here.
|
||||
// Keep old value unless the replacement is a PathClassLoader-compatible one.
|
||||
if (!isPathClassLoader(obj)) {
|
||||
Log.w(TAG, "Skip replacing classloader field " + field.getName() + " with " + obj.getClass().getName());
|
||||
continue;
|
||||
}
|
||||
XposedHelpers.setObjectField(stubLoadedApk, field.getName(), obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPathClassLoader(Object loader) {
|
||||
if (!(loader instanceof ClassLoader)) return false;
|
||||
Class<?> clz = loader.getClass();
|
||||
while (clz != null) {
|
||||
if ("dalvik.system.PathClassLoader".equals(clz.getName())) {
|
||||
return true;
|
||||
}
|
||||
clz = clz.getSuperclass();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,34 @@
|
|||
package org.lsposed.npatch.loader;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.app.AppComponentFactory;
|
||||
import android.app.LoadedApk;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import org.lsposed.lspd.impl.LSPosedContext;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedInit;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
import io.github.libxposed.api.XposedModuleInterface;
|
||||
|
||||
public class LSPLoader {
|
||||
private static final String TAG = "NPatch";
|
||||
private static final int MODERN_DISPATCH_MAX_RETRY = 8;
|
||||
private static final long MODERN_DISPATCH_RETRY_DELAY_MS = 400L;
|
||||
|
||||
public static void initModules(LoadedApk loadedApk) {
|
||||
if (loadedApk == null) {
|
||||
Log.w(TAG, "Skip initModules: loadedApk is null");
|
||||
return;
|
||||
}
|
||||
XposedInit.loadedPackagesInProcess.add(loadedApk.getPackageName());
|
||||
setPackageNameForResDir(loadedApk.getPackageName(), loadedApk.getResDir());
|
||||
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(
|
||||
|
|
@ -26,6 +41,117 @@ public class LSPLoader {
|
|||
XC_LoadPackage.callAll(lpparam);
|
||||
}
|
||||
|
||||
public static void dispatchModernCallbacksAsync(LoadedApk loadedApk) {
|
||||
dispatchModernCallbacksAsync(loadedApk, 0);
|
||||
}
|
||||
|
||||
public static boolean dispatchModernCallbacksNow(LoadedApk loadedApk) {
|
||||
return tryDispatchModernCallbacks(loadedApk, 0);
|
||||
}
|
||||
|
||||
private static void dispatchModernCallbacksAsync(LoadedApk loadedApk, int retry) {
|
||||
if (loadedApk == null) return;
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.postDelayed(() -> {
|
||||
if (tryDispatchModernCallbacks(loadedApk, retry)) {
|
||||
return;
|
||||
}
|
||||
if (retry < MODERN_DISPATCH_MAX_RETRY) {
|
||||
dispatchModernCallbacksAsync(loadedApk, retry + 1);
|
||||
} else {
|
||||
Log.w(TAG, "Modern callback dispatch exhausted retries for " + loadedApk.getPackageName());
|
||||
}
|
||||
}, retry == 0 ? 600L : MODERN_DISPATCH_RETRY_DELAY_MS);
|
||||
}
|
||||
|
||||
private static boolean tryDispatchModernCallbacks(LoadedApk loadedApk, int retry) {
|
||||
final var packageName = loadedApk.getPackageName();
|
||||
final var appInfo = loadedApk.getApplicationInfo();
|
||||
final var classLoader = loadedApk.getClassLoader();
|
||||
if (appInfo == null || classLoader == null) {
|
||||
Log.w(TAG, "Modern dispatch retry " + retry + ": appInfo/classLoader not ready");
|
||||
return false;
|
||||
}
|
||||
final var defaultClassLoader = resolveDefaultClassLoader(loadedApk, classLoader);
|
||||
|
||||
// Check one typical app class path availability before dispatching callbacks.
|
||||
// This reduces chances of firing too early in patched process bootstrap.
|
||||
try {
|
||||
classLoader.loadClass("android.app.Activity");
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "Modern dispatch retry " + retry + ": classloader not stable yet");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
LSPosedContext.callOnPackageLoaded(new XposedModuleInterface.PackageLoadedParam() {
|
||||
@Override
|
||||
public String getPackageName() {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationInfo getApplicationInfo() {
|
||||
return appInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getDefaultClassLoader() {
|
||||
return defaultClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader() {
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFirstPackage() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "callOnPackageLoaded failed for " + packageName + " retry=" + retry, t);
|
||||
}
|
||||
|
||||
Object appComponentFactory = null;
|
||||
try {
|
||||
appComponentFactory = XposedHelpers.getObjectField(loadedApk, "mAppComponentFactory");
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && !(appComponentFactory instanceof AppComponentFactory)) {
|
||||
appComponentFactory = new AppComponentFactory();
|
||||
}
|
||||
try {
|
||||
LSPosedContext.callOnPackageReady(
|
||||
packageName,
|
||||
appInfo,
|
||||
true,
|
||||
defaultClassLoader,
|
||||
classLoader,
|
||||
appComponentFactory
|
||||
);
|
||||
Log.i(TAG, "Modern callbacks dispatched for " + packageName + " on retry=" + retry);
|
||||
return true;
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "callOnPackageReady failed for " + packageName + " retry=" + retry, t);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static ClassLoader resolveDefaultClassLoader(LoadedApk loadedApk, ClassLoader fallback) {
|
||||
try {
|
||||
var field = LoadedApk.class.getDeclaredField("mDefaultClassLoader");
|
||||
field.setAccessible(true);
|
||||
var value = field.get(loadedApk);
|
||||
if (value instanceof ClassLoader) {
|
||||
return (ClassLoader) value;
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
private static void setPackageNameForResDir(String packageName, String resDir) {
|
||||
try {
|
||||
// Use reflection to avoid direct type reference to android.content.res.XResources
|
||||
|
|
@ -36,7 +162,8 @@ public class LSPLoader {
|
|||
Method setMethod = xResourcesClass.getMethod("setPackageNameForResDir", String.class, String.class);
|
||||
setMethod.invoke(null, packageName, resDir);
|
||||
} catch (Throwable e) {
|
||||
Log.w(TAG, "XResources.setPackageNameForResDir not available, skipping resource dir setup", e);
|
||||
// Resource hooks are optional in the patch-loader path; avoid noisy stack traces.
|
||||
Log.w(TAG, "XResources.setPackageNameForResDir not available, skipping");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
|
|
@ -62,7 +63,11 @@ public class IntegrApplicationService extends ILSPApplicationService.Stub {
|
|||
module.apkPath = cacheApkPath;
|
||||
module.packageName = packageName;
|
||||
module.file = ModuleLoader.loadModule(cacheApkPath);
|
||||
modules.add(module);
|
||||
if (module.file != null) {
|
||||
modules.add(module);
|
||||
} else {
|
||||
Log.w(TAG, "Skip invalid embedded module: " + packageName);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error when initializing IntegrApplicationServiceClient", e);
|
||||
|
|
@ -71,12 +76,12 @@ public class IntegrApplicationService extends ILSPApplicationService.Stub {
|
|||
|
||||
@Override
|
||||
public List<Module> getLegacyModulesList() throws RemoteException {
|
||||
return modules;
|
||||
return modules.stream().filter(m -> m.file != null && m.file.legacy).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Module> getModulesList() throws RemoteException {
|
||||
return new ArrayList<>();
|
||||
return modules.stream().filter(m -> m.file != null && !m.file.legacy).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -99,4 +104,4 @@ public class IntegrApplicationService extends ILSPApplicationService.Stub {
|
|||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@ import java.io.File;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
||||
private static final String TAG = "NPatch";
|
||||
private static final String AUTHORITY = "org.lsposed.npatch.manager.provider.config";
|
||||
private static final String AUTHORITY = "org.gkmspatch.manager.provider.config";
|
||||
private static final Uri PROVIDER_URI = Uri.parse("content://" + AUTHORITY + "/config");
|
||||
|
||||
private final List<Module> cachedModule;
|
||||
|
|
@ -70,8 +71,12 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
|||
m.packageName = pkgName;
|
||||
m.apkPath = path;
|
||||
m.file = ModuleLoader.loadModule(m.apkPath);
|
||||
cachedModule.add(m);
|
||||
Log.i(TAG, "Loaded cached module " + pkgName);
|
||||
if (m.file != null) {
|
||||
cachedModule.add(m);
|
||||
Log.i(TAG, "Loaded cached module " + pkgName);
|
||||
} else {
|
||||
Log.w(TAG, "Skip cached module without valid entry list: " + pkgName);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Failed to load cached module " + pkgName, e);
|
||||
}
|
||||
|
|
@ -111,8 +116,12 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
|||
|
||||
if (m.apkPath != null && new File(m.apkPath).exists()) {
|
||||
m.file = ModuleLoader.loadModule(m.apkPath);
|
||||
cachedModule.add(m);
|
||||
Log.i(TAG, "NeoLocal: Loaded module " + pkgName);
|
||||
if (m.file != null) {
|
||||
cachedModule.add(m);
|
||||
Log.i(TAG, "NeoLocal: Loaded module " + pkgName);
|
||||
} else {
|
||||
Log.w(TAG, "NeoLocal: Skip invalid module package " + pkgName);
|
||||
}
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "NeoLocal: Failed to load " + pkgName, e);
|
||||
|
|
@ -121,12 +130,12 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
|||
|
||||
@Override
|
||||
public List<Module> getLegacyModulesList() throws RemoteException {
|
||||
return cachedModule;
|
||||
return cachedModule.stream().filter(m -> m.file != null && m.file.legacy).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Module> getModulesList() throws RemoteException {
|
||||
return new ArrayList<>();
|
||||
return cachedModule.stream().filter(m -> m.file != null && !m.file.legacy).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -60,8 +60,16 @@ public class ModuleLoader {
|
|||
var moduleLibraryNames = new ArrayList<String>(1);
|
||||
try (var apkFile = new ZipFile(path)) {
|
||||
readDexes(apkFile, preLoadedDexes);
|
||||
readName(apkFile, "assets/xposed_init", moduleClassNames);
|
||||
readName(apkFile, "assets/native_init", moduleLibraryNames);
|
||||
// Prefer modern metadata first.
|
||||
readName(apkFile, "META-INF/xposed/java_init.list", moduleClassNames);
|
||||
if (moduleClassNames.isEmpty()) {
|
||||
file.legacy = true;
|
||||
readName(apkFile, "assets/xposed_init", moduleClassNames);
|
||||
readName(apkFile, "assets/native_init", moduleLibraryNames);
|
||||
} else {
|
||||
file.legacy = false;
|
||||
readName(apkFile, "META-INF/xposed/native_init.list", moduleLibraryNames);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Can not open " + path, e);
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ public class Constants {
|
|||
|
||||
final static public String PATCH_FILE_SUFFIX = "-npatched.apk";
|
||||
final static public String PROXY_APP_COMPONENT_FACTORY = "org.lsposed.npatch.metaloader.LSPAppComponentFactoryStub";
|
||||
final static public String MANAGER_PACKAGE_NAME = "org.lsposed.npatch";
|
||||
final static public String MANAGER_PACKAGE_NAME = "org.gkmspatch";
|
||||
final static public String REAL_GMS_PACKAGE_NAME = "com.google.android.gms";
|
||||
final static public int MIN_ROLLING_VERSION_CODE = 400;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue