Compare commits

..

No commits in common. "41872d8261a956a8bde1b51cf29914bb2e9f36df" and "7e5e2d6bfd39f591050ade8637028966f8a5774c" have entirely different histories.

21 changed files with 59 additions and 424 deletions

2
.gitmodules vendored
View File

@ -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

View File

@ -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

@ -1 +1 @@
Subproject commit 62ea99fa35dba5454b93db6eab8aced25c157383
Subproject commit 83180d20de3ee55e8901cb1e74bf9895b793c629

View File

@ -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"

View File

@ -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)
}

View File

@ -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))
}

View File

@ -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>()

View File

@ -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,

View File

@ -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,

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -23,7 +23,6 @@ android {
externalNativeBuild {
cmake {
path("src/main/jni/CMakeLists.txt")
version = "3.31.6"
}
}
namespace = "org.lsposed.npatch.loader"

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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);
}
}
}
}

View File

@ -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) {

View File

@ -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)

View File

@ -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");

View File

@ -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;

View File

@ -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;
}
}