Compare commits
10 Commits
7e5e2d6bfd
...
41872d8261
| Author | SHA1 | Date |
|---|---|---|
|
|
41872d8261 | |
|
|
e640da4f57 | |
|
|
01d344ed01 | |
|
|
46df4e85cf | |
|
|
db64adde94 | |
|
|
43ca1640ab | |
|
|
cf6993baa0 | |
|
|
cf50194b29 | |
|
|
3c8964bc83 | |
|
|
df7bf3eb58 |
|
|
@ -4,5 +4,5 @@
|
||||||
branch = android10-release
|
branch = android10-release
|
||||||
[submodule "core"]
|
[submodule "core"]
|
||||||
path = core
|
path = core
|
||||||
url = https://github.com/HSSkyBoy/LSPosed.git
|
url = https://github.com/HSSkyBoy/LSPosed-JingMatrix.git
|
||||||
branch = master
|
branch = master
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,13 @@ val (coreCommitCount, coreLatestTag) = FileRepositoryBuilder().setGitDir(rootPro
|
||||||
val coreCommitCount =
|
val coreCommitCount =
|
||||||
git.log()
|
git.log()
|
||||||
.add(repo.refDatabase.exactRef("HEAD").objectId)
|
.add(repo.refDatabase.exactRef("HEAD").objectId)
|
||||||
.call().count() + 4200
|
.call().count()
|
||||||
val ver = git.describe()
|
val ver = git.describe()
|
||||||
.setTags(true)
|
.setTags(true)
|
||||||
.setAbbrev(0).call().removePrefix("v")
|
.setAbbrev(0).call().removePrefix("v")
|
||||||
coreCommitCount to ver
|
coreCommitCount to ver
|
||||||
}
|
}
|
||||||
}.getOrNull() ?: (1 to "1.0")
|
}.getOrNull() ?: (1145 to "1.0")
|
||||||
|
|
||||||
// sync from https://github.com/JingMartix/LSPosed/blob/master/build.gradle.kts
|
// sync from https://github.com/JingMartix/LSPosed/blob/master/build.gradle.kts
|
||||||
val defaultManagerPackageName by extra("org.lsposed.npatch")
|
val defaultManagerPackageName by extra("org.lsposed.npatch")
|
||||||
|
|
|
||||||
2
core
2
core
|
|
@ -1 +1 @@
|
||||||
Subproject commit 83180d20de3ee55e8901cb1e74bf9895b793c629
|
Subproject commit 62ea99fa35dba5454b93db6eab8aced25c157383
|
||||||
|
|
@ -27,7 +27,9 @@ android {
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = true
|
isMinifyEnabled = true // 启用 R8/ProGuard 进行代码压缩、优化和混淆。
|
||||||
|
isShrinkResources = true // 启用资源缩减,移除未被引用的资源文件。
|
||||||
|
isDebuggable = false // 发布版本禁止调试。
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
|
|
|
||||||
|
|
@ -201,9 +201,7 @@ object NPackageManager {
|
||||||
splits.add(dst.absolutePath)
|
splits.add(dst.absolutePath)
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
if (primary == null) {
|
if (primary == null) primary = appInfo
|
||||||
primary = appInfo
|
|
||||||
}
|
|
||||||
val label = lspApp.packageManager.getApplicationLabel(appInfo).toString()
|
val label = lspApp.packageManager.getApplicationLabel(appInfo).toString()
|
||||||
AppInfo(appInfo, label)
|
AppInfo(appInfo, label)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ object Patcher {
|
||||||
}
|
}
|
||||||
if (config.injectProvider) add("--provider")
|
if (config.injectProvider) add("--provider")
|
||||||
if(injectDex) add("--injectdex")
|
if(injectDex) add("--injectdex")
|
||||||
|
if (config.useMicroG) add("--useMicroG")
|
||||||
if (!MyKeyStore.useDefault) {
|
if (!MyKeyStore.useDefault) {
|
||||||
addAll(arrayOf("-k", MyKeyStore.file.path, Configs.keyStorePassword, Configs.keyStoreAlias, Configs.keyStoreAliasPassword))
|
addAll(arrayOf("-k", MyKeyStore.file.path, Configs.keyStorePassword, Configs.keyStoreAlias, Configs.keyStoreAliasPassword))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,15 @@ object ConfigManager {
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val dispatcher = Dispatchers.Default.limitedParallelism(1)
|
private val dispatcher = Dispatchers.Default.limitedParallelism(1)
|
||||||
|
|
||||||
private val db: LSPDatabase = Room.databaseBuilder(
|
private val db: LSPDatabase by lazy {
|
||||||
lspApp, LSPDatabase::class.java, "modules_config.db"
|
Room.databaseBuilder(
|
||||||
).build()
|
lspApp, LSPDatabase::class.java, "modules_config.db"
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
|
||||||
private val moduleDao = db.moduleDao()
|
|
||||||
private val scopeDao = db.scopeDao()
|
private val moduleDao get() = db.moduleDao()
|
||||||
|
private val scopeDao get() = db.scopeDao()
|
||||||
|
|
||||||
private val loadedModules = mutableMapOf<Module, org.lsposed.lspd.models.Module>()
|
private val loadedModules = mutableMapOf<Module, org.lsposed.lspd.models.Module>()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,13 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
|
||||||
title = stringResource(R.string.patch_inject_mt_provider),
|
title = stringResource(R.string.patch_inject_mt_provider),
|
||||||
desc = stringResource(R.string.patch_inject_mt_provider_desc)
|
desc = stringResource(R.string.patch_inject_mt_provider_desc)
|
||||||
)
|
)
|
||||||
|
SettingsCheckBox(
|
||||||
|
modifier = Modifier.clickable { viewModel.useMicroG = !viewModel.useMicroG },
|
||||||
|
checked = viewModel.useMicroG,
|
||||||
|
icon = Icons.Outlined.CloudSync,
|
||||||
|
title = stringResource(R.string.patch_use_microg),
|
||||||
|
desc = stringResource(R.string.patch_use_microg_desc)
|
||||||
|
)
|
||||||
SettingsCheckBox(
|
SettingsCheckBox(
|
||||||
modifier = Modifier.clickable { viewModel.outputLog = !viewModel.outputLog },
|
modifier = Modifier.clickable { viewModel.outputLog = !viewModel.outputLog },
|
||||||
checked = viewModel.outputLog,
|
checked = viewModel.outputLog,
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ class NewPatchViewModel : ViewModel() {
|
||||||
var injectDex by mutableStateOf(false)
|
var injectDex by mutableStateOf(false)
|
||||||
var injectProvider by mutableStateOf(false)
|
var injectProvider by mutableStateOf(false)
|
||||||
var outputLog by mutableStateOf(true)
|
var outputLog by mutableStateOf(true)
|
||||||
|
var useMicroG by mutableStateOf(false)
|
||||||
var embeddedModules = emptyList<AppInfo>()
|
var embeddedModules = emptyList<AppInfo>()
|
||||||
|
|
||||||
lateinit var patchApp: AppInfo
|
lateinit var patchApp: AppInfo
|
||||||
|
|
@ -99,7 +100,7 @@ class NewPatchViewModel : ViewModel() {
|
||||||
private fun submitPatch() {
|
private fun submitPatch() {
|
||||||
Log.d(TAG, "Submit Patch")
|
Log.d(TAG, "Submit Patch")
|
||||||
if (useManager) embeddedModules = emptyList()
|
if (useManager) embeddedModules = emptyList()
|
||||||
val config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null, injectProvider, outputLog, newPackageName)
|
val config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null, injectProvider, outputLog, newPackageName, useMicroG)
|
||||||
patchOptions = Patcher.Options(
|
patchOptions = Patcher.Options(
|
||||||
newPackageName = newPackageName,
|
newPackageName = newPackageName,
|
||||||
injectDex = injectDex,
|
injectDex = injectDex,
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
<string name="patch_sigbypasslv0">lv0: 关闭</string>
|
<string name="patch_sigbypasslv0">lv0: 关闭</string>
|
||||||
<string name="patch_sigbypasslv1">lv1: 绕过 PM</string>
|
<string name="patch_sigbypasslv1">lv1: 绕过 PM</string>
|
||||||
<string name="patch_sigbypasslv2">lv2: 绕过 PM + openat (libc)</string>
|
<string name="patch_sigbypasslv2">lv2: 绕过 PM + openat (libc)</string>
|
||||||
<string name="patch_sigbypasslv3">lv3: 绕过 PM + openat (libc) + SVC (仅 arm64) (测试版)</string>
|
<string name="patch_sigbypasslv3">lv3: 绕过 PM + openat (libc) + SVC (仅 arm64)</string>
|
||||||
<string name="patch_override_version_code">覆写版本号</string>
|
<string name="patch_override_version_code">覆写版本号</string>
|
||||||
<string name="patch_override_version_code_desc">将修补的 App 版本号重写为 1\n这将允许后续降级安装,并且通常来说这不会影响应用实际感知到的版本号</string>
|
<string name="patch_override_version_code_desc">将修补的 App 版本号重写为 1\n这将允许后续降级安装,并且通常来说这不会影响应用实际感知到的版本号</string>
|
||||||
<string name="patch_new_package">修补新包名</string>
|
<string name="patch_new_package">修补新包名</string>
|
||||||
|
|
@ -73,7 +73,8 @@
|
||||||
<string name="patch_inject_mt_provider_desc">注入文件提供器以在没有 Root 权限的情况下管理 data 目录的文件 (来自 MT 管理器)</string>
|
<string name="patch_inject_mt_provider_desc">注入文件提供器以在没有 Root 权限的情况下管理 data 目录的文件 (来自 MT 管理器)</string>
|
||||||
<string name="patch_inject_dex">注入加载器 Dex</string>
|
<string name="patch_inject_dex">注入加载器 Dex</string>
|
||||||
<string name="patch_inject_dex_desc">对那些需要孤立服务进程的应用程序,譬如说浏览器的渲染引擎,请勾选此选项以确保他们正常运行</string>
|
<string name="patch_inject_dex_desc">对那些需要孤立服务进程的应用程序,譬如说浏览器的渲染引擎,请勾选此选项以确保他们正常运行</string>
|
||||||
<string name="patch_output_log_to_media">日志输出到 Media 目录</string>
|
<string name="patch_use_microg">强制启用 MicroG 支持</string>
|
||||||
|
<string name="patch_use_microg_desc">重新导向 GMS 请求至社区版 MicroG。适用于 YouTube 等 Google 应用程序,需自行安装对应的 MicroG 服务。</string> <string name="patch_output_log_to_media">日志输出到 Media 目录</string>
|
||||||
<string name="patch_output_log_to_media_desc">将模块的 Xposed 日志输出到目标应用的 Media 目录</string>
|
<string name="patch_output_log_to_media_desc">将模块的 Xposed 日志输出到目标应用的 Media 目录</string>
|
||||||
<string name="patch_start">开始修补</string>
|
<string name="patch_start">开始修补</string>
|
||||||
<string name="patch_return">返回</string>
|
<string name="patch_return">返回</string>
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
<string name="patch_sigbypasslv0">lv0: 關閉</string>
|
<string name="patch_sigbypasslv0">lv0: 關閉</string>
|
||||||
<string name="patch_sigbypasslv1">lv1: 繞過 PM</string>
|
<string name="patch_sigbypasslv1">lv1: 繞過 PM</string>
|
||||||
<string name="patch_sigbypasslv2">lv2: 繞過 PM + openat (libc)</string>
|
<string name="patch_sigbypasslv2">lv2: 繞過 PM + openat (libc)</string>
|
||||||
<string name="patch_sigbypasslv3">lv3: 繞過 PM + openat (libc) + SVC (僅 arm64) (測試用)</string>
|
<string name="patch_sigbypasslv3">lv3: 繞過 PM + openat (libc) + SVC (僅 64 位)</string>
|
||||||
<string name="patch_override_version_code">覆蓋版本編號</string>
|
<string name="patch_override_version_code">覆蓋版本編號</string>
|
||||||
<string name="patch_override_version_code_desc">將打包應用程式的版本編號改成 1\n允許以後降級安裝,一般來說,這不會影響應用程式實際感知的版本編號。</string>
|
<string name="patch_override_version_code_desc">將打包應用程式的版本編號改成 1\n允許以後降級安裝,一般來說,這不會影響應用程式實際感知的版本編號。</string>
|
||||||
<string name="patch_new_package">修補新套件名</string>
|
<string name="patch_new_package">修補新套件名</string>
|
||||||
|
|
@ -73,6 +73,8 @@
|
||||||
<string name="patch_inject_mt_provider_desc">注入檔案选取器以在沒有 Root 權限的情況下管理 data 目錄的檔案(來自 MT 管理器)</string>
|
<string name="patch_inject_mt_provider_desc">注入檔案选取器以在沒有 Root 權限的情況下管理 data 目錄的檔案(來自 MT 管理器)</string>
|
||||||
<string name="patch_inject_dex">注入載入器 Dex</string>
|
<string name="patch_inject_dex">注入載入器 Dex</string>
|
||||||
<string name="patch_inject_dex_desc">對那些需要孤立服務程序的應用程式,譬如說瀏覽器的渲染引擎,請勾選此選項以確保他們正常執行</string>
|
<string name="patch_inject_dex_desc">對那些需要孤立服務程序的應用程式,譬如說瀏覽器的渲染引擎,請勾選此選項以確保他們正常執行</string>
|
||||||
|
<string name="patch_use_microg">強制啟用 MicroG 支援</string>
|
||||||
|
<string name="patch_use_microg_desc">重新導向 GMS 請求至社群版 MicroG。適用於 YouTube 等 Google 應用程式,需自行安裝對應的 MicroG 服務。</string>
|
||||||
<string name="patch_output_log_to_media">日誌輸出到 Media 目錄</string>
|
<string name="patch_output_log_to_media">日誌輸出到 Media 目錄</string>
|
||||||
<string name="patch_output_log_to_media_desc">將模組的 Xposed 日誌輸出到目標應用的 Media 目錄</string>
|
<string name="patch_output_log_to_media_desc">將模組的 Xposed 日誌輸出到目標應用的 Media 目錄</string>
|
||||||
<string name="patch_start">開始打包</string>
|
<string name="patch_start">開始打包</string>
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
<string name="patch_sigbypasslv0">lv0: Off</string>
|
<string name="patch_sigbypasslv0">lv0: Off</string>
|
||||||
<string name="patch_sigbypasslv1">lv1: Bypass PM</string>
|
<string name="patch_sigbypasslv1">lv1: Bypass PM</string>
|
||||||
<string name="patch_sigbypasslv2">lv2: Bypass PM + openat (libc)</string>
|
<string name="patch_sigbypasslv2">lv2: Bypass PM + openat (libc)</string>
|
||||||
<string name="patch_sigbypasslv3">lv3: Bypass PM + openat(libc) + SVC (v8a only) (testing)</string>
|
<string name="patch_sigbypasslv3">lv3: Bypass PM + openat(libc) + SVC (64-bit only)</string>
|
||||||
<string name="patch_new_package">Patch New PackageName</string>
|
<string name="patch_new_package">Patch New PackageName</string>
|
||||||
<string name="hint_patch_new_package">Input a new package for app</string>
|
<string name="hint_patch_new_package">Input a new package for app</string>
|
||||||
<string name="patch_override_version_code">Override version code</string>
|
<string name="patch_override_version_code">Override version code</string>
|
||||||
|
|
@ -75,6 +75,8 @@
|
||||||
<string name="patch_inject_mt_provider_desc">Inject file providers to manage files in the data directory without root privileges (From MT Manager)</string>
|
<string name="patch_inject_mt_provider_desc">Inject file providers to manage files in the data directory without root privileges (From MT Manager)</string>
|
||||||
<string name="patch_inject_dex">Inject loader dex</string>
|
<string name="patch_inject_dex">Inject loader dex</string>
|
||||||
<string name="patch_inject_dex_desc">For applications with isolated services, such as the render engines of browsers, please turn on this option to ensure that they work properly.</string>
|
<string name="patch_inject_dex_desc">For applications with isolated services, such as the render engines of browsers, please turn on this option to ensure that they work properly.</string>
|
||||||
|
<string name="patch_use_microg">Force enable MicroG support</string>
|
||||||
|
<string name="patch_use_microg_desc">Redirect GMS requests to the community version of MicroG (such as ReVanced GmsCore). Applicable to Google apps like YouTube, requires manually installing the corresponding MicroG service.</string>
|
||||||
<string name="patch_output_log_to_media">Output Log to Media Directory</string>
|
<string name="patch_output_log_to_media">Output Log to Media Directory</string>
|
||||||
<string name="patch_output_log_to_media_desc">Output the Xposed log to the target application Media directory.</string>
|
<string name="patch_output_log_to_media_desc">Output the Xposed log to the target application Media directory.</string>
|
||||||
<string name="patch_start">Start Patch</string>
|
<string name="patch_start">Start Patch</string>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ android {
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path("src/main/jni/CMakeLists.txt")
|
path("src/main/jni/CMakeLists.txt")
|
||||||
|
version = "3.31.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
namespace = "org.lsposed.npatch.loader"
|
namespace = "org.lsposed.npatch.loader"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,256 @@
|
||||||
|
package org.lsposed.npatch.loader;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.Signature;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.lsposed.npatch.share.Constants;
|
||||||
|
|
||||||
|
import de.robv.android.xposed.XC_MethodHook;
|
||||||
|
import de.robv.android.xposed.XposedBridge;
|
||||||
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
|
|
||||||
|
public class GmsRedirector {
|
||||||
|
private static final String TAG = "NPatch-GmsRedirect";
|
||||||
|
private static final String REAL_GMS = Constants.REAL_GMS_PACKAGE_NAME;
|
||||||
|
|
||||||
|
// 鎖定社群主流的 MicroG 套件名稱
|
||||||
|
private static final String[] MICROG_PACKAGES = {
|
||||||
|
"app.revanced.android.gms", // ReVanced GmsCore (推薦)
|
||||||
|
"org.microg.gms", // Original MicroG
|
||||||
|
};
|
||||||
|
|
||||||
|
private static String targetGms = null;
|
||||||
|
private static String originalSignature;
|
||||||
|
|
||||||
|
public static void activate(Context context, String origSig) {
|
||||||
|
originalSignature = origSig;
|
||||||
|
|
||||||
|
targetGms = findInstalledMicroG(context);
|
||||||
|
if (targetGms == null) {
|
||||||
|
Log.w(TAG, "No MicroG/GmsCore found! GMS redirect disabled.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.i(TAG, "Activating GMS redirect: " + REAL_GMS + " -> " + targetGms);
|
||||||
|
|
||||||
|
hookIntentSetPackage();
|
||||||
|
hookIntentSetComponent();
|
||||||
|
hookIntentResolve();
|
||||||
|
hookContentResolverAcquire();
|
||||||
|
hookPackageManagerGetPackageInfo(context);
|
||||||
|
|
||||||
|
Log.i(TAG, "GMS redirect hooks installed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String findInstalledMicroG(Context context) {
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
for (String pkg : MICROG_PACKAGES) {
|
||||||
|
try {
|
||||||
|
pm.getPackageInfo(pkg, 0);
|
||||||
|
return pkg;
|
||||||
|
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String redirectPackage(String pkg) {
|
||||||
|
if (REAL_GMS.equals(pkg) || "com.google.android.gsf".equals(pkg)) {
|
||||||
|
return targetGms;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String redirectAuthority(String authority) {
|
||||||
|
if (authority == null) return null;
|
||||||
|
if (authority.startsWith(REAL_GMS + ".")) {
|
||||||
|
return targetGms + authority.substring(REAL_GMS.length());
|
||||||
|
}
|
||||||
|
if (authority.equals(REAL_GMS)) {
|
||||||
|
return targetGms;
|
||||||
|
}
|
||||||
|
if (authority.startsWith("com.google.android.gsf")) {
|
||||||
|
return authority.replace("com.google.android.gsf", targetGms);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void hookIntentSetPackage() {
|
||||||
|
try {
|
||||||
|
XposedBridge.hookAllMethods(Intent.class, "setPackage", new XC_MethodHook() {
|
||||||
|
@Override
|
||||||
|
protected void beforeHookedMethod(MethodHookParam param) {
|
||||||
|
String pkg = (String) param.args[0];
|
||||||
|
String redirected = redirectPackage(pkg);
|
||||||
|
if (redirected != null) param.args[0] = redirected;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.e(TAG, "Failed to hook Intent.setPackage", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void hookIntentSetComponent() {
|
||||||
|
try {
|
||||||
|
XposedBridge.hookAllMethods(Intent.class, "setComponent", new XC_MethodHook() {
|
||||||
|
@Override
|
||||||
|
protected void beforeHookedMethod(MethodHookParam param) {
|
||||||
|
ComponentName cn = (ComponentName) param.args[0];
|
||||||
|
if (cn != null) {
|
||||||
|
String redirected = redirectPackage(cn.getPackageName());
|
||||||
|
if (redirected != null) {
|
||||||
|
param.args[0] = new ComponentName(redirected, cn.getClassName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.e(TAG, "Failed to hook Intent.setComponent", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void hookIntentResolve() {
|
||||||
|
try {
|
||||||
|
XposedBridge.hookAllConstructors(Intent.class, new XC_MethodHook() {
|
||||||
|
@Override
|
||||||
|
protected void afterHookedMethod(MethodHookParam param) {
|
||||||
|
Intent intent = (Intent) param.thisObject;
|
||||||
|
ComponentName cn = intent.getComponent();
|
||||||
|
if (cn != null) {
|
||||||
|
String redirected = redirectPackage(cn.getPackageName());
|
||||||
|
if (redirected != null) {
|
||||||
|
intent.setComponent(new ComponentName(redirected, cn.getClassName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String pkg = intent.getPackage();
|
||||||
|
if (pkg != null) {
|
||||||
|
String redirected = redirectPackage(pkg);
|
||||||
|
if (redirected != null) {
|
||||||
|
intent.setPackage(redirected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.e(TAG, "Failed to hook Intent constructors", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void hookContentResolverAcquire() {
|
||||||
|
try {
|
||||||
|
for (String method : new String[]{
|
||||||
|
"acquireProvider", "acquireContentProviderClient",
|
||||||
|
"acquireUnstableProvider", "acquireUnstableContentProviderClient"
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
XposedBridge.hookAllMethods(ContentResolver.class, method, new XC_MethodHook() {
|
||||||
|
@Override
|
||||||
|
protected void beforeHookedMethod(MethodHookParam param) {
|
||||||
|
if (param.args[0] instanceof Uri) {
|
||||||
|
Uri uri = (Uri) param.args[0];
|
||||||
|
String newAuth = redirectAuthority(uri.getAuthority());
|
||||||
|
if (newAuth != null) {
|
||||||
|
param.args[0] = uri.buildUpon().authority(newAuth).build();
|
||||||
|
}
|
||||||
|
} else if (param.args[0] instanceof String) {
|
||||||
|
String newAuth = redirectAuthority((String) param.args[0]);
|
||||||
|
if (newAuth != null) {
|
||||||
|
param.args[0] = newAuth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 攔截 ContentResolver.call,遇到 SecurityException 則自動重試
|
||||||
|
try {
|
||||||
|
XposedBridge.hookAllMethods(ContentResolver.class, "call", new XC_MethodHook() {
|
||||||
|
@Override
|
||||||
|
protected void beforeHookedMethod(MethodHookParam param) {
|
||||||
|
for (int i = 0; i < param.args.length; i++) {
|
||||||
|
if (param.args[i] instanceof Uri) {
|
||||||
|
Uri uri = (Uri) param.args[i];
|
||||||
|
String newAuth = redirectAuthority(uri.getAuthority());
|
||||||
|
if (newAuth != null) {
|
||||||
|
param.args[i] = uri.buildUpon().authority(newAuth).build();
|
||||||
|
}
|
||||||
|
} else if (param.args[i] instanceof String && i == 0) {
|
||||||
|
String newAuth = redirectAuthority((String) param.args[i]);
|
||||||
|
if (newAuth != null) {
|
||||||
|
param.args[i] = newAuth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void afterHookedMethod(MethodHookParam param) {
|
||||||
|
if (param.getThrowable() instanceof SecurityException) {
|
||||||
|
String msg = param.getThrowable().getMessage();
|
||||||
|
if (msg != null && (msg.contains("GoogleCertificatesRslt") ||
|
||||||
|
msg.contains("not allowed") ||
|
||||||
|
msg.contains("Access denied"))) {
|
||||||
|
Log.i(TAG, "GMS rejected call, retrying with MicroG");
|
||||||
|
for (int i = 0; i < param.args.length; i++) {
|
||||||
|
if (param.args[i] instanceof Uri) {
|
||||||
|
Uri uri = (Uri) param.args[i];
|
||||||
|
String authority = uri.getAuthority();
|
||||||
|
if (authority != null && authority.contains(REAL_GMS)) {
|
||||||
|
param.args[i] = uri.buildUpon()
|
||||||
|
.authority(authority.replace(REAL_GMS, targetGms))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
} else if (param.args[i] instanceof String && i == 0) {
|
||||||
|
String s = (String) param.args[i];
|
||||||
|
if (s.contains(REAL_GMS)) {
|
||||||
|
param.args[i] = s.replace(REAL_GMS, targetGms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
param.setThrowable(null);
|
||||||
|
param.setResult(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.e(TAG, "Failed to hook ContentResolver", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void hookPackageManagerGetPackageInfo(Context context) {
|
||||||
|
try {
|
||||||
|
XposedHelpers.findAndHookMethod(
|
||||||
|
context.getPackageManager().getClass(),
|
||||||
|
"getPackageInfo",
|
||||||
|
String.class, int.class,
|
||||||
|
new XC_MethodHook() {
|
||||||
|
@Override
|
||||||
|
protected void afterHookedMethod(MethodHookParam param) {
|
||||||
|
PackageInfo pi = (PackageInfo) param.getResult();
|
||||||
|
if (pi != null && targetGms != null) {
|
||||||
|
if (targetGms.equals(pi.packageName) && (((int) param.args[1]) & PackageManager.GET_SIGNATURES) != 0) {
|
||||||
|
if (originalSignature != null && !originalSignature.isEmpty()) {
|
||||||
|
try {
|
||||||
|
byte[] sigBytes = android.util.Base64.decode(originalSignature, android.util.Base64.DEFAULT);
|
||||||
|
pi.signatures = new Signature[]{new Signature(sigBytes)};
|
||||||
|
} catch (Throwable ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
Log.e(TAG, "Failed to hook PackageManager.getPackageInfo", t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -45,7 +45,6 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import dalvik.system.DexFile;
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
import hidden.HiddenApiBridge;
|
import hidden.HiddenApiBridge;
|
||||||
|
|
@ -146,6 +145,11 @@ public class LSPApplication {
|
||||||
switchAllClassLoader();
|
switchAllClassLoader();
|
||||||
SigBypass.doSigBypass(context, config.sigBypassLevel);
|
SigBypass.doSigBypass(context, config.sigBypassLevel);
|
||||||
|
|
||||||
|
if (config.useMicroG) {
|
||||||
|
Log.i(TAG, "Activating MicroG redirect via NPatch");
|
||||||
|
GmsRedirector.activate(context, config.originalSignature);
|
||||||
|
}
|
||||||
|
|
||||||
Log.i(TAG, "NPatch bootstrap completed");
|
Log.i(TAG, "NPatch bootstrap completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,19 +205,24 @@ public class LSPApplication {
|
||||||
appLoadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
|
appLoadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
|
||||||
|
|
||||||
if (config.injectProvider && providerPath != null) {
|
if (config.injectProvider && providerPath != null) {
|
||||||
ClassLoader loader = appLoadedApk.getClassLoader();
|
try {
|
||||||
Object dexPathList = XposedHelpers.getObjectField(loader, "pathList");
|
ClassLoader loader = appLoadedApk.getClassLoader();
|
||||||
Object dexElements = XposedHelpers.getObjectField(dexPathList, "dexElements");
|
Object dexPathList = XposedHelpers.getObjectField(loader, "pathList");
|
||||||
int length = Array.getLength(dexElements);
|
Object dexElements = XposedHelpers.getObjectField(dexPathList, "dexElements");
|
||||||
Object newElements = Array.newInstance(dexElements.getClass().getComponentType(), length + 1);
|
int length = Array.getLength(dexElements);
|
||||||
System.arraycopy(dexElements, 0, newElements, 0, length);
|
Object newElements = Array.newInstance(dexElements.getClass().getComponentType(), length + 1);
|
||||||
|
System.arraycopy(dexElements, 0, newElements, 0, length);
|
||||||
|
|
||||||
DexFile dexFile = new DexFile(providerPath.toString());
|
// Use reflection for DexFile to handle deprecation on Android 14+
|
||||||
Object element = XposedHelpers.newInstance(XposedHelpers.findClass("dalvik.system.DexPathList$Element", loader), new Class[]{
|
Class<?> dexFileClass = Class.forName("dalvik.system.DexFile");
|
||||||
DexFile.class
|
Object dexFile = dexFileClass.getConstructor(String.class).newInstance(providerPath.toString());
|
||||||
}, dexFile);
|
Class<?> elementClass = Class.forName("dalvik.system.DexPathList$Element");
|
||||||
Array.set(newElements, length, element);
|
Object element = elementClass.getConstructor(dexFileClass).newInstance(dexFile);
|
||||||
XposedHelpers.setObjectField(dexPathList, "dexElements", newElements);
|
Array.set(newElements, length, element);
|
||||||
|
XposedHelpers.setObjectField(dexPathList, "dexElements", newElements);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.e(TAG, "Failed to inject provider dex: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
XposedHelpers.setObjectField(mBoundApplication, "info", appLoadedApk);
|
XposedHelpers.setObjectField(mBoundApplication, "info", appLoadedApk);
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,20 @@ package org.lsposed.npatch.loader;
|
||||||
|
|
||||||
import android.app.ActivityThread;
|
import android.app.ActivityThread;
|
||||||
import android.app.LoadedApk;
|
import android.app.LoadedApk;
|
||||||
import android.content.res.XResources;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedInit;
|
import de.robv.android.xposed.XposedInit;
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
|
|
||||||
public class LSPLoader {
|
public class LSPLoader {
|
||||||
|
private static final String TAG = "NPatch";
|
||||||
|
|
||||||
public static void initModules(LoadedApk loadedApk) {
|
public static void initModules(LoadedApk loadedApk) {
|
||||||
XposedInit.loadedPackagesInProcess.add(loadedApk.getPackageName());
|
XposedInit.loadedPackagesInProcess.add(loadedApk.getPackageName());
|
||||||
XResources.setPackageNameForResDir(loadedApk.getPackageName(), loadedApk.getResDir());
|
setPackageNameForResDir(loadedApk.getPackageName(), loadedApk.getResDir());
|
||||||
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(
|
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(
|
||||||
XposedBridge.sLoadedPackageCallbacks);
|
XposedBridge.sLoadedPackageCallbacks);
|
||||||
lpparam.packageName = loadedApk.getPackageName();
|
lpparam.packageName = loadedApk.getPackageName();
|
||||||
|
|
@ -21,4 +25,18 @@ public class LSPLoader {
|
||||||
lpparam.isFirstApplication = true;
|
lpparam.isFirstApplication = true;
|
||||||
XC_LoadPackage.callAll(lpparam);
|
XC_LoadPackage.callAll(lpparam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void setPackageNameForResDir(String packageName, String resDir) {
|
||||||
|
try {
|
||||||
|
// Use reflection to avoid direct type reference to android.content.res.XResources
|
||||||
|
// which fails class resolution on Android 16+ due to strict boot classloader
|
||||||
|
// namespace delegation for the android.content.res.* package.
|
||||||
|
ClassLoader cl = LSPLoader.class.getClassLoader();
|
||||||
|
Class<?> xResourcesClass = cl.loadClass("android.content.res.XResources");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.lsposed.npatch.service;
|
package org.lsposed.npatch.service;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
|
@ -10,6 +11,8 @@ import android.os.ParcelFileDescriptor;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.lsposed.npatch.util.ModuleLoader;
|
import org.lsposed.npatch.util.ModuleLoader;
|
||||||
import org.lsposed.lspd.models.Module;
|
import org.lsposed.lspd.models.Module;
|
||||||
import org.lsposed.lspd.service.ILSPApplicationService;
|
import org.lsposed.lspd.service.ILSPApplicationService;
|
||||||
|
|
@ -29,6 +32,49 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
||||||
public NeoLocalApplicationService(Context context) {
|
public NeoLocalApplicationService(Context context) {
|
||||||
cachedModule = Collections.synchronizedList(new ArrayList<>());
|
cachedModule = Collections.synchronizedList(new ArrayList<>());
|
||||||
loadModulesFromProvider(context);
|
loadModulesFromProvider(context);
|
||||||
|
|
||||||
|
if (cachedModule.isEmpty()) {
|
||||||
|
Log.w(TAG, "NeoLocal: Provider returned empty, falling back to local cache.");
|
||||||
|
loadModulesFromCache(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadModulesFromCache(Context context) {
|
||||||
|
try {
|
||||||
|
SharedPreferences shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
|
||||||
|
String jsonStr = shared.getString("modules", "[]");
|
||||||
|
JSONArray jsonArray = new JSONArray(jsonStr);
|
||||||
|
PackageManager pm = context.getPackageManager();
|
||||||
|
|
||||||
|
Log.i(TAG, "NeoLocal: Loading from cache: " + jsonStr);
|
||||||
|
|
||||||
|
for (int i = 0; i < jsonArray.length(); i++) {
|
||||||
|
JSONObject obj = jsonArray.getJSONObject(i);
|
||||||
|
String packageName = obj.optString("packageName");
|
||||||
|
String path = obj.optString("path");
|
||||||
|
|
||||||
|
if (path != null && !path.isEmpty() && new File(path).exists()) {
|
||||||
|
loadModuleByPath(packageName, path);
|
||||||
|
} else if (packageName != null) {
|
||||||
|
loadSingleModule(pm, packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "NeoLocal: Failed to load from cache", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadModuleByPath(String pkgName, String path) {
|
||||||
|
try {
|
||||||
|
Module m = new Module();
|
||||||
|
m.packageName = pkgName;
|
||||||
|
m.apkPath = path;
|
||||||
|
m.file = ModuleLoader.loadModule(m.apkPath);
|
||||||
|
cachedModule.add(m);
|
||||||
|
Log.i(TAG, "Loaded cached module " + pkgName);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.e(TAG, "Failed to load cached module " + pkgName, e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadModulesFromProvider(Context context) {
|
private void loadModulesFromProvider(Context context) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
project(npatch)
|
project(npatch)
|
||||||
cmake_minimum_required(VERSION 3.28)
|
cmake_minimum_required(VERSION 3.31.1)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
add_subdirectory(${CORE_ROOT} core)
|
add_subdirectory(${CORE_ROOT} core)
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,12 @@ public class NPatch {
|
||||||
@Parameter(names = {"--provider"}, description = "Inject Provider to manager data files")
|
@Parameter(names = {"--provider"}, description = "Inject Provider to manager data files")
|
||||||
private boolean isInjectProvider = false;
|
private boolean isInjectProvider = false;
|
||||||
|
|
||||||
|
@Parameter(names = {"--installerSource"}, description = "Original app installer source")
|
||||||
|
private String installerSource = "";
|
||||||
|
|
||||||
|
@Parameter(names = {"--useMicroG"}, description = "Redirect GMS calls to community MicroG")
|
||||||
|
private boolean useMicroG = false;
|
||||||
|
|
||||||
@Parameter(names = {"--outputLog"}, description = "Output Log to Media")
|
@Parameter(names = {"--outputLog"}, description = "Output Log to Media")
|
||||||
private boolean outputLog = true;
|
private boolean outputLog = true;
|
||||||
|
|
||||||
|
|
@ -118,7 +124,7 @@ public class NPatch {
|
||||||
private static final ZFileOptions Z_FILE_OPTIONS = new ZFileOptions()
|
private static final ZFileOptions Z_FILE_OPTIONS = new ZFileOptions()
|
||||||
.setNoTimestamps(true)
|
.setNoTimestamps(true)
|
||||||
.setAlignmentRule(AlignmentRules.compose(
|
.setAlignmentRule(AlignmentRules.compose(
|
||||||
AlignmentRules.constantForSuffix(".so", 4096),
|
AlignmentRules.constantForSuffix(".so", 16384),
|
||||||
AlignmentRules.constantForSuffix(ORIGINAL_APK_ASSET_PATH, 4096),
|
AlignmentRules.constantForSuffix(ORIGINAL_APK_ASSET_PATH, 4096),
|
||||||
AlignmentRules.constantForSuffix(".arsc", 4)
|
AlignmentRules.constantForSuffix(".arsc", 4)
|
||||||
));
|
));
|
||||||
|
|
@ -287,10 +293,10 @@ public class NPatch {
|
||||||
|
|
||||||
logger.i("Patching apk...");
|
logger.i("Patching apk...");
|
||||||
// modify manifest
|
// modify manifest
|
||||||
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, sigbypassLevel, originalSignature, appComponentFactory, isInjectProvider, outputLog, newPackage);
|
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, sigbypassLevel, originalSignature, appComponentFactory, isInjectProvider, outputLog, newPackage, useMicroG);
|
||||||
final var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
final var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
||||||
final var metadata = Base64.getEncoder().encodeToString(configBytes);
|
final var metadata = Base64.getEncoder().encodeToString(configBytes);
|
||||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata, minSdkVersion, pair.packageName, newPackage))) {
|
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata, minSdkVersion, pair.packageName, newPackage, originalSignature))) {
|
||||||
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new PatchError("Error when modifying manifest", e);
|
throw new PatchError("Error when modifying manifest", e);
|
||||||
|
|
@ -422,44 +428,52 @@ public class NPatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] modifyManifestFile(InputStream is, String metadata, int minSdkVersion, String originPackage, String newPackage) throws IOException {
|
private byte[] modifyManifestFile(InputStream is, String metadata, int minSdkVersion, String originPackage, String newPackage, String originalSignature) throws IOException {
|
||||||
ModificationProperty property = new ModificationProperty();
|
ModificationProperty property = new ModificationProperty();
|
||||||
|
|
||||||
String targetPackage = (newPackage != null && !newPackage.isEmpty()) ? newPackage : originPackage;
|
String targetPackage = (newPackage != null && !newPackage.isEmpty()) ? newPackage : originPackage;
|
||||||
|
|
||||||
if (overrideVersionCode)
|
if (minSdkVersion > 0)
|
||||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, 1));
|
property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, minSdkVersion));
|
||||||
if (minSdkVersion < 28)
|
else
|
||||||
property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, 27));
|
property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, 27));
|
||||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
||||||
property.addApplicationAttribute(new AttributeItem("appComponentFactory", PROXY_APP_COMPONENT_FACTORY));
|
property.addApplicationAttribute(new AttributeItem("appComponentFactory", PROXY_APP_COMPONENT_FACTORY));
|
||||||
|
property.addApplicationAttribute(new AttributeItem("isSplitRequired", false));
|
||||||
|
|
||||||
if (!targetPackage.equals(originPackage)) {
|
if (!targetPackage.equals(originPackage)) {
|
||||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, targetPackage).setNamespace(null));
|
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.PACKAGE, targetPackage).setNamespace(null));
|
||||||
}
|
}
|
||||||
property.setPermissionMapper((type, permission) -> {
|
|
||||||
if (permission.startsWith(originPackage)) {
|
|
||||||
return permission.replaceFirst(originPackage, targetPackage);
|
|
||||||
}
|
|
||||||
if (permission.startsWith("android")
|
|
||||||
|| permission.startsWith("com.android")
|
|
||||||
|| permission.startsWith("com.google.android")) {
|
|
||||||
return permission;
|
|
||||||
}
|
|
||||||
return targetPackage + "_" + permission;
|
|
||||||
});
|
|
||||||
|
|
||||||
property.setAuthorityMapper(value -> {
|
modules.forEach(module -> {
|
||||||
if (value.startsWith(originPackage)) {
|
property.addMetaData(new ModificationProperty.MetaData("xposedmodule", "true"));
|
||||||
return value.replaceFirst(originPackage, targetPackage);
|
property.addMetaData(new ModificationProperty.MetaData("xposeddescription", "NPatch Embed Module"));
|
||||||
}
|
property.addMetaData(new ModificationProperty.MetaData("xposedminversion", "93"));
|
||||||
return targetPackage + "_" + value;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
property.addMetaData(new ModificationProperty.MetaData("npatch", metadata));
|
property.addMetaData(new ModificationProperty.MetaData("npatch", metadata));
|
||||||
|
|
||||||
|
// 注入 MicroG 偽裝簽名與權限
|
||||||
|
if (useMicroG && originalSignature != null && !originalSignature.isEmpty()) {
|
||||||
|
try {
|
||||||
|
byte[] sigBytes = Base64.getDecoder().decode(originalSignature);
|
||||||
|
StringBuilder hex = new StringBuilder();
|
||||||
|
for (byte b : sigBytes) {
|
||||||
|
hex.append(String.format("%02x", b));
|
||||||
|
}
|
||||||
|
property.addMetaData(new ModificationProperty.MetaData("fake-signature", hex.toString()));
|
||||||
|
property.addUsesPermission("android.permission.FAKE_PACKAGE_SIGNATURE");
|
||||||
|
logger.d("Added fake-signature metadata for MicroG compatibility");
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.e("Failed to add fake-signature: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: replace query_all with queries -> manager
|
// TODO: replace query_all with queries -> manager
|
||||||
if (useManager)
|
if (useManager)
|
||||||
property.addUsesPermission("android.permission.QUERY_ALL_PACKAGES");
|
property.addUsesPermission("android.permission.QUERY_ALL_PACKAGES");
|
||||||
|
|
||||||
|
// 處理注入 Provider 的邏輯
|
||||||
if (isInjectProvider){
|
if (isInjectProvider){
|
||||||
HashMap<String,String> providerMap = new HashMap<>();
|
HashMap<String,String> providerMap = new HashMap<>();
|
||||||
providerMap.put("name","bin.mt.file.content.MTDataFilesProvider");
|
providerMap.put("name","bin.mt.file.content.MTDataFilesProvider");
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ public class Constants {
|
||||||
final static public String PATCH_FILE_SUFFIX = "-npatched.apk";
|
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 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.lsposed.npatch";
|
||||||
|
final static public String REAL_GMS_PACKAGE_NAME = "com.google.android.gms";
|
||||||
final static public int MIN_ROLLING_VERSION_CODE = 400;
|
final static public int MIN_ROLLING_VERSION_CODE = 400;
|
||||||
|
|
||||||
final static public int SIGBYPASS_LV_DISABLE = 0;
|
final static public int SIGBYPASS_LV_DISABLE = 0;
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ public class PatchConfig {
|
||||||
public final LSPConfig lspConfig;
|
public final LSPConfig lspConfig;
|
||||||
public final String managerPackageName;
|
public final String managerPackageName;
|
||||||
public final String newPackage;
|
public final String newPackage;
|
||||||
|
public final boolean useMicroG;
|
||||||
|
|
||||||
public PatchConfig(
|
public PatchConfig(
|
||||||
boolean useManager,
|
boolean useManager,
|
||||||
|
|
@ -23,7 +24,8 @@ public class PatchConfig {
|
||||||
String appComponentFactory,
|
String appComponentFactory,
|
||||||
boolean injectProvider,
|
boolean injectProvider,
|
||||||
boolean outputLog,
|
boolean outputLog,
|
||||||
String newPackage
|
String newPackage,
|
||||||
|
boolean useMicroG
|
||||||
) {
|
) {
|
||||||
this.useManager = useManager;
|
this.useManager = useManager;
|
||||||
this.debuggable = debuggable;
|
this.debuggable = debuggable;
|
||||||
|
|
@ -36,5 +38,6 @@ public class PatchConfig {
|
||||||
this.managerPackageName = Constants.MANAGER_PACKAGE_NAME;
|
this.managerPackageName = Constants.MANAGER_PACKAGE_NAME;
|
||||||
this.newPackage = newPackage;
|
this.newPackage = newPackage;
|
||||||
this.outputLog = outputLog;
|
this.outputLog = outputLog;
|
||||||
|
this.useMicroG = useMicroG;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue