Compare commits
No commits in common. "41872d8261a956a8bde1b51cf29914bb2e9f36df" and "7e5e2d6bfd39f591050ade8637028966f8a5774c" have entirely different histories.
41872d8261
...
7e5e2d6bfd
|
|
@ -4,5 +4,5 @@
|
|||
branch = android10-release
|
||||
[submodule "core"]
|
||||
path = core
|
||||
url = https://github.com/HSSkyBoy/LSPosed-JingMatrix.git
|
||||
url = https://github.com/HSSkyBoy/LSPosed.git
|
||||
branch = master
|
||||
|
|
|
|||
|
|
@ -37,13 +37,13 @@ val (coreCommitCount, coreLatestTag) = FileRepositoryBuilder().setGitDir(rootPro
|
|||
val coreCommitCount =
|
||||
git.log()
|
||||
.add(repo.refDatabase.exactRef("HEAD").objectId)
|
||||
.call().count()
|
||||
.call().count() + 4200
|
||||
val ver = git.describe()
|
||||
.setTags(true)
|
||||
.setAbbrev(0).call().removePrefix("v")
|
||||
coreCommitCount to ver
|
||||
}
|
||||
}.getOrNull() ?: (1145 to "1.0")
|
||||
}.getOrNull() ?: (1 to "1.0")
|
||||
|
||||
// sync from https://github.com/JingMartix/LSPosed/blob/master/build.gradle.kts
|
||||
val defaultManagerPackageName by extra("org.lsposed.npatch")
|
||||
|
|
|
|||
2
core
2
core
|
|
@ -1 +1 @@
|
|||
Subproject commit 62ea99fa35dba5454b93db6eab8aced25c157383
|
||||
Subproject commit 83180d20de3ee55e8901cb1e74bf9895b793c629
|
||||
|
|
@ -27,9 +27,7 @@ android {
|
|||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true // 启用 R8/ProGuard 进行代码压缩、优化和混淆。
|
||||
isShrinkResources = true // 启用资源缩减,移除未被引用的资源文件。
|
||||
isDebuggable = false // 发布版本禁止调试。
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
|
|
|
|||
|
|
@ -201,7 +201,9 @@ object NPackageManager {
|
|||
splits.add(dst.absolutePath)
|
||||
return@mapNotNull null
|
||||
}
|
||||
if (primary == null) primary = appInfo
|
||||
if (primary == null) {
|
||||
primary = appInfo
|
||||
}
|
||||
val label = lspApp.packageManager.getApplicationLabel(appInfo).toString()
|
||||
AppInfo(appInfo, label)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ object Patcher {
|
|||
}
|
||||
if (config.injectProvider) add("--provider")
|
||||
if(injectDex) add("--injectdex")
|
||||
if (config.useMicroG) add("--useMicroG")
|
||||
if (!MyKeyStore.useDefault) {
|
||||
addAll(arrayOf("-k", MyKeyStore.file.path, Configs.keyStorePassword, Configs.keyStoreAlias, Configs.keyStoreAliasPassword))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,15 +20,12 @@ object ConfigManager {
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val dispatcher = Dispatchers.Default.limitedParallelism(1)
|
||||
|
||||
private val db: LSPDatabase by lazy {
|
||||
Room.databaseBuilder(
|
||||
lspApp, LSPDatabase::class.java, "modules_config.db"
|
||||
).build()
|
||||
}
|
||||
private val db: LSPDatabase = Room.databaseBuilder(
|
||||
lspApp, LSPDatabase::class.java, "modules_config.db"
|
||||
).build()
|
||||
|
||||
|
||||
private val moduleDao get() = db.moduleDao()
|
||||
private val scopeDao get() = db.scopeDao()
|
||||
private val moduleDao = db.moduleDao()
|
||||
private val scopeDao = db.scopeDao()
|
||||
|
||||
private val loadedModules = mutableMapOf<Module, org.lsposed.lspd.models.Module>()
|
||||
|
||||
|
|
|
|||
|
|
@ -365,13 +365,6 @@ private fun PatchOptionsBody(modifier: Modifier, onAddEmbed: () -> Unit) {
|
|||
title = stringResource(R.string.patch_inject_mt_provider),
|
||||
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(
|
||||
modifier = Modifier.clickable { viewModel.outputLog = !viewModel.outputLog },
|
||||
checked = viewModel.outputLog,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,6 @@ class NewPatchViewModel : ViewModel() {
|
|||
var injectDex by mutableStateOf(false)
|
||||
var injectProvider by mutableStateOf(false)
|
||||
var outputLog by mutableStateOf(true)
|
||||
var useMicroG by mutableStateOf(false)
|
||||
var embeddedModules = emptyList<AppInfo>()
|
||||
|
||||
lateinit var patchApp: AppInfo
|
||||
|
|
@ -100,7 +99,7 @@ class NewPatchViewModel : ViewModel() {
|
|||
private fun submitPatch() {
|
||||
Log.d(TAG, "Submit Patch")
|
||||
if (useManager) embeddedModules = emptyList()
|
||||
val config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null, injectProvider, outputLog, newPackageName, useMicroG)
|
||||
val config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null, injectProvider, outputLog, newPackageName)
|
||||
patchOptions = Patcher.Options(
|
||||
newPackageName = newPackageName,
|
||||
injectDex = injectDex,
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
<string name="patch_sigbypasslv0">lv0: 关闭</string>
|
||||
<string name="patch_sigbypasslv1">lv1: 绕过 PM</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_desc">将修补的 App 版本号重写为 1\n这将允许后续降级安装,并且通常来说这不会影响应用实际感知到的版本号</string>
|
||||
<string name="patch_new_package">修补新包名</string>
|
||||
|
|
@ -73,8 +73,7 @@
|
|||
<string name="patch_inject_mt_provider_desc">注入文件提供器以在没有 Root 权限的情况下管理 data 目录的文件 (来自 MT 管理器)</string>
|
||||
<string name="patch_inject_dex">注入加载器 Dex</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_start">开始修补</string>
|
||||
<string name="patch_return">返回</string>
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
<string name="patch_sigbypasslv0">lv0: 關閉</string>
|
||||
<string name="patch_sigbypasslv1">lv1: 繞過 PM</string>
|
||||
<string name="patch_sigbypasslv2">lv2: 繞過 PM + openat (libc)</string>
|
||||
<string name="patch_sigbypasslv3">lv3: 繞過 PM + openat (libc) + SVC (僅 64 位)</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_desc">將打包應用程式的版本編號改成 1\n允許以後降級安裝,一般來說,這不會影響應用程式實際感知的版本編號。</string>
|
||||
<string name="patch_new_package">修補新套件名</string>
|
||||
|
|
@ -73,8 +73,6 @@
|
|||
<string name="patch_inject_mt_provider_desc">注入檔案选取器以在沒有 Root 權限的情況下管理 data 目錄的檔案(來自 MT 管理器)</string>
|
||||
<string name="patch_inject_dex">注入載入器 Dex</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_desc">將模組的 Xposed 日誌輸出到目標應用的 Media 目錄</string>
|
||||
<string name="patch_start">開始打包</string>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
<string name="patch_sigbypasslv0">lv0: Off</string>
|
||||
<string name="patch_sigbypasslv1">lv1: Bypass PM</string>
|
||||
<string name="patch_sigbypasslv2">lv2: Bypass PM + openat (libc)</string>
|
||||
<string name="patch_sigbypasslv3">lv3: Bypass PM + openat(libc) + SVC (64-bit only)</string>
|
||||
<string name="patch_sigbypasslv3">lv3: Bypass PM + openat(libc) + SVC (v8a only) (testing)</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="patch_override_version_code">Override version code</string>
|
||||
|
|
@ -75,8 +75,6 @@
|
|||
<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_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_desc">Output the Xposed log to the target application Media directory.</string>
|
||||
<string name="patch_start">Start Patch</string>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ android {
|
|||
externalNativeBuild {
|
||||
cmake {
|
||||
path("src/main/jni/CMakeLists.txt")
|
||||
version = "3.31.6"
|
||||
}
|
||||
}
|
||||
namespace = "org.lsposed.npatch.loader"
|
||||
|
|
|
|||
|
|
@ -1,256 +0,0 @@
|
|||
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,6 +45,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import dalvik.system.DexFile;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import hidden.HiddenApiBridge;
|
||||
|
|
@ -145,11 +146,6 @@ public class LSPApplication {
|
|||
switchAllClassLoader();
|
||||
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");
|
||||
}
|
||||
|
||||
|
|
@ -205,24 +201,19 @@ public class LSPApplication {
|
|||
appLoadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
|
||||
|
||||
if (config.injectProvider && providerPath != null) {
|
||||
try {
|
||||
ClassLoader loader = appLoadedApk.getClassLoader();
|
||||
Object dexPathList = XposedHelpers.getObjectField(loader, "pathList");
|
||||
Object dexElements = XposedHelpers.getObjectField(dexPathList, "dexElements");
|
||||
int length = Array.getLength(dexElements);
|
||||
Object newElements = Array.newInstance(dexElements.getClass().getComponentType(), length + 1);
|
||||
System.arraycopy(dexElements, 0, newElements, 0, length);
|
||||
ClassLoader loader = appLoadedApk.getClassLoader();
|
||||
Object dexPathList = XposedHelpers.getObjectField(loader, "pathList");
|
||||
Object dexElements = XposedHelpers.getObjectField(dexPathList, "dexElements");
|
||||
int length = Array.getLength(dexElements);
|
||||
Object newElements = Array.newInstance(dexElements.getClass().getComponentType(), length + 1);
|
||||
System.arraycopy(dexElements, 0, newElements, 0, length);
|
||||
|
||||
// Use reflection for DexFile to handle deprecation on Android 14+
|
||||
Class<?> dexFileClass = Class.forName("dalvik.system.DexFile");
|
||||
Object dexFile = dexFileClass.getConstructor(String.class).newInstance(providerPath.toString());
|
||||
Class<?> elementClass = Class.forName("dalvik.system.DexPathList$Element");
|
||||
Object element = elementClass.getConstructor(dexFileClass).newInstance(dexFile);
|
||||
Array.set(newElements, length, element);
|
||||
XposedHelpers.setObjectField(dexPathList, "dexElements", newElements);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Failed to inject provider dex: " + e.getMessage(), e);
|
||||
}
|
||||
DexFile dexFile = new DexFile(providerPath.toString());
|
||||
Object element = XposedHelpers.newInstance(XposedHelpers.findClass("dalvik.system.DexPathList$Element", loader), new Class[]{
|
||||
DexFile.class
|
||||
}, dexFile);
|
||||
Array.set(newElements, length, element);
|
||||
XposedHelpers.setObjectField(dexPathList, "dexElements", newElements);
|
||||
}
|
||||
|
||||
XposedHelpers.setObjectField(mBoundApplication, "info", appLoadedApk);
|
||||
|
|
|
|||
|
|
@ -2,20 +2,16 @@ package org.lsposed.npatch.loader;
|
|||
|
||||
import android.app.ActivityThread;
|
||||
import android.app.LoadedApk;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import android.content.res.XResources;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedInit;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
|
||||
public class LSPLoader {
|
||||
private static final String TAG = "NPatch";
|
||||
|
||||
public static void initModules(LoadedApk loadedApk) {
|
||||
XposedInit.loadedPackagesInProcess.add(loadedApk.getPackageName());
|
||||
setPackageNameForResDir(loadedApk.getPackageName(), loadedApk.getResDir());
|
||||
XResources.setPackageNameForResDir(loadedApk.getPackageName(), loadedApk.getResDir());
|
||||
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(
|
||||
XposedBridge.sLoadedPackageCallbacks);
|
||||
lpparam.packageName = loadedApk.getPackageName();
|
||||
|
|
@ -25,18 +21,4 @@ public class LSPLoader {
|
|||
lpparam.isFirstApplication = true;
|
||||
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,7 +1,6 @@
|
|||
package org.lsposed.npatch.service;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
|
|
@ -11,8 +10,6 @@ import android.os.ParcelFileDescriptor;
|
|||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.lsposed.npatch.util.ModuleLoader;
|
||||
import org.lsposed.lspd.models.Module;
|
||||
import org.lsposed.lspd.service.ILSPApplicationService;
|
||||
|
|
@ -32,49 +29,6 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
|||
public NeoLocalApplicationService(Context context) {
|
||||
cachedModule = Collections.synchronizedList(new ArrayList<>());
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
project(npatch)
|
||||
cmake_minimum_required(VERSION 3.31.1)
|
||||
cmake_minimum_required(VERSION 3.28)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
add_subdirectory(${CORE_ROOT} core)
|
||||
|
|
|
|||
|
|
@ -89,12 +89,6 @@ public class NPatch {
|
|||
@Parameter(names = {"--provider"}, description = "Inject Provider to manager data files")
|
||||
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")
|
||||
private boolean outputLog = true;
|
||||
|
||||
|
|
@ -124,7 +118,7 @@ public class NPatch {
|
|||
private static final ZFileOptions Z_FILE_OPTIONS = new ZFileOptions()
|
||||
.setNoTimestamps(true)
|
||||
.setAlignmentRule(AlignmentRules.compose(
|
||||
AlignmentRules.constantForSuffix(".so", 16384),
|
||||
AlignmentRules.constantForSuffix(".so", 4096),
|
||||
AlignmentRules.constantForSuffix(ORIGINAL_APK_ASSET_PATH, 4096),
|
||||
AlignmentRules.constantForSuffix(".arsc", 4)
|
||||
));
|
||||
|
|
@ -293,10 +287,10 @@ public class NPatch {
|
|||
|
||||
logger.i("Patching apk...");
|
||||
// modify manifest
|
||||
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, sigbypassLevel, originalSignature, appComponentFactory, isInjectProvider, outputLog, newPackage, useMicroG);
|
||||
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, sigbypassLevel, originalSignature, appComponentFactory, isInjectProvider, outputLog, newPackage);
|
||||
final var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
||||
final var metadata = Base64.getEncoder().encodeToString(configBytes);
|
||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata, minSdkVersion, pair.packageName, newPackage, originalSignature))) {
|
||||
try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata, minSdkVersion, pair.packageName, newPackage))) {
|
||||
dstZFile.add(ANDROID_MANIFEST_XML, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when modifying manifest", e);
|
||||
|
|
@ -428,52 +422,44 @@ public class NPatch {
|
|||
}
|
||||
}
|
||||
|
||||
private byte[] modifyManifestFile(InputStream is, String metadata, int minSdkVersion, String originPackage, String newPackage, String originalSignature) throws IOException {
|
||||
private byte[] modifyManifestFile(InputStream is, String metadata, int minSdkVersion, String originPackage, String newPackage) throws IOException {
|
||||
ModificationProperty property = new ModificationProperty();
|
||||
|
||||
String targetPackage = (newPackage != null && !newPackage.isEmpty()) ? newPackage : originPackage;
|
||||
|
||||
if (minSdkVersion > 0)
|
||||
property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, minSdkVersion));
|
||||
else
|
||||
if (overrideVersionCode)
|
||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, 1));
|
||||
if (minSdkVersion < 28)
|
||||
property.addUsesSdkAttribute(new AttributeItem(NodeValue.UsesSDK.MIN_SDK_VERSION, 27));
|
||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
||||
property.addApplicationAttribute(new AttributeItem("appComponentFactory", PROXY_APP_COMPONENT_FACTORY));
|
||||
property.addApplicationAttribute(new AttributeItem("isSplitRequired", false));
|
||||
|
||||
if (!targetPackage.equals(originPackage)) {
|
||||
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;
|
||||
});
|
||||
|
||||
modules.forEach(module -> {
|
||||
property.addMetaData(new ModificationProperty.MetaData("xposedmodule", "true"));
|
||||
property.addMetaData(new ModificationProperty.MetaData("xposeddescription", "NPatch Embed Module"));
|
||||
property.addMetaData(new ModificationProperty.MetaData("xposedminversion", "93"));
|
||||
property.setAuthorityMapper(value -> {
|
||||
if (value.startsWith(originPackage)) {
|
||||
return value.replaceFirst(originPackage, targetPackage);
|
||||
}
|
||||
return targetPackage + "_" + value;
|
||||
});
|
||||
|
||||
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
|
||||
if (useManager)
|
||||
property.addUsesPermission("android.permission.QUERY_ALL_PACKAGES");
|
||||
|
||||
// 處理注入 Provider 的邏輯
|
||||
if (isInjectProvider){
|
||||
HashMap<String,String> providerMap = new HashMap<>();
|
||||
providerMap.put("name","bin.mt.file.content.MTDataFilesProvider");
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ 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 REAL_GMS_PACKAGE_NAME = "com.google.android.gms";
|
||||
final static public int MIN_ROLLING_VERSION_CODE = 400;
|
||||
|
||||
final static public int SIGBYPASS_LV_DISABLE = 0;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ public class PatchConfig {
|
|||
public final LSPConfig lspConfig;
|
||||
public final String managerPackageName;
|
||||
public final String newPackage;
|
||||
public final boolean useMicroG;
|
||||
|
||||
public PatchConfig(
|
||||
boolean useManager,
|
||||
|
|
@ -24,8 +23,7 @@ public class PatchConfig {
|
|||
String appComponentFactory,
|
||||
boolean injectProvider,
|
||||
boolean outputLog,
|
||||
String newPackage,
|
||||
boolean useMicroG
|
||||
String newPackage
|
||||
) {
|
||||
this.useManager = useManager;
|
||||
this.debuggable = debuggable;
|
||||
|
|
@ -38,6 +36,5 @@ public class PatchConfig {
|
|||
this.managerPackageName = Constants.MANAGER_PACKAGE_NAME;
|
||||
this.newPackage = newPackage;
|
||||
this.outputLog = outputLog;
|
||||
this.useMicroG = useMicroG;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue