feat: support inject MT Manager DocumentsProvider

Co-Authored-By: Hicores <me@hicore.cc>
This commit is contained in:
NkBe 2025-10-08 14:16:03 +08:00
parent 5d21b9c599
commit a2b9e529ca
No known key found for this signature in database
GPG Key ID: 525137026FF031DF
13 changed files with 91 additions and 9 deletions

Binary file not shown.

View File

@ -40,6 +40,14 @@
android:exported="true" android:exported="true"
android:targetActivity="org.lsposed.lspatch.ui.activity.MainActivity"> android:targetActivity="org.lsposed.lspatch.ui.activity.MainActivity">
<intent-filter> <intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />

Binary file not shown.

View File

@ -35,6 +35,7 @@ object Patcher {
embeddedModules?.forEach { embeddedModules?.forEach {
add("-m"); add(it) add("-m"); add(it)
} }
if (config.injectProvider) add("--provider")
if(injectDex) add("--injectdex") if(injectDex) add("--injectdex")
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))

View File

@ -566,6 +566,7 @@ private fun InstallDialog(patchApp: AppInfo, onFinish: (Int, String?) -> Unit) {
LaunchedEffect(uninstallFirst) { LaunchedEffect(uninstallFirst) {
if (!uninstallFirst && installing == 0) { if (!uninstallFirst && installing == 0) {
onFinish(LSPPackageManager.STATUS_USER_CANCELLED, "User cancelled")
doInstall() doInstall()
} }
} }

View File

@ -45,6 +45,7 @@ class NewPatchViewModel : ViewModel() {
var overrideVersionCode by mutableStateOf(false) var overrideVersionCode by mutableStateOf(false)
var sigBypassLevel by mutableStateOf(2) var sigBypassLevel by mutableStateOf(2)
var injectDex by mutableStateOf(false) var injectDex by mutableStateOf(false)
var injectProvider by mutableStateOf(false)
var outputLog by mutableStateOf(true) var outputLog by mutableStateOf(true)
var embeddedModules = emptyList<AppInfo>() var embeddedModules = emptyList<AppInfo>()
@ -96,9 +97,9 @@ 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, outputLog, newPackageName) val config = PatchConfig(useManager, debuggable, overrideVersionCode, sigBypassLevel, null, null, injectProvider, outputLog, newPackageName)
patchOptions = Patcher.Options( patchOptions = Patcher.Options(
newPackageName = newPackageName, newPackageName = newPackageName,
injectDex = injectDex, injectDex = injectDex,
@ -110,7 +111,7 @@ class NewPatchViewModel : ViewModel() {
} }
private suspend fun launchPatch() { private suspend fun launchPatch() {
logger.i("Launch patch") logger.i("Launch Patch")
patchState = try { patchState = try {
Patcher.patch(logger, patchOptions) Patcher.patch(logger, patchOptions)
PatchState.FINISHED PatchState.FINISHED

View File

@ -65,6 +65,8 @@
<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>
<string name="hint_patch_new_package">请输入新的包名</string> <string name="hint_patch_new_package">请输入新的包名</string>
<string name="patch_inject_mt_provider">注入文件提供器</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_output_log_to_media">日志输出到 Media 目录</string>

View File

@ -65,6 +65,8 @@
<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>
<string name="hint_patch_new_package">請輸入新的套件名</string> <string name="hint_patch_new_package">請輸入新的套件名</string>
<string name="patch_inject_mt_provider">注入檔案选取器</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_output_log_to_media">日誌輸出到 Media 目錄</string>

View File

@ -67,6 +67,8 @@
<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>
<string name="patch_override_version_code_desc">Override the patched app\'s version code to 1\nThis allows downgrade installation in the future, and generally this will not affect the version code actually perceived by the application</string> <string name="patch_override_version_code_desc">Override the patched app\'s version code to 1\nThis allows downgrade installation in the future, and generally this will not affect the version code actually perceived by the application</string>
<string name="patch_inject_mt_provider">Inject Files Provider</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_output_log_to_media">Output Log to Media Directory</string> <string name="patch_output_log_to_media">Output Log to Media Directory</string>

View File

@ -2,6 +2,7 @@ package org.lsposed.lspatch.loader;
import static org.lsposed.lspatch.share.Constants.CONFIG_ASSET_PATH; import static org.lsposed.lspatch.share.Constants.CONFIG_ASSET_PATH;
import static org.lsposed.lspatch.share.Constants.ORIGINAL_APK_ASSET_PATH; import static org.lsposed.lspatch.share.Constants.ORIGINAL_APK_ASSET_PATH;
import static org.lsposed.lspatch.share.Constants.PROVIDER_DEX_ASSET_PATH;
import android.app.ActivityThread; import android.app.ActivityThread;
import android.app.LoadedApk; import android.app.LoadedApk;
@ -28,6 +29,7 @@ import org.lsposed.lspd.service.ILSPApplicationService;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -150,6 +152,7 @@ public class LSPApplication {
try (ZipFile sourceFile = new ZipFile(appInfo.sourceDir)) { try (ZipFile sourceFile = new ZipFile(appInfo.sourceDir)) {
cacheApkPath = originPath.resolve(sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc() + ".apk"); cacheApkPath = originPath.resolve(sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc() + ".apk");
} }
String sourceFileaa = appInfo.sourceDir;
appInfo.sourceDir = cacheApkPath.toString(); appInfo.sourceDir = cacheApkPath.toString();
appInfo.publicSourceDir = cacheApkPath.toString(); appInfo.publicSourceDir = cacheApkPath.toString();
@ -163,6 +166,20 @@ public class LSPApplication {
Files.copy(is, cacheApkPath); Files.copy(is, cacheApkPath);
} }
} }
Path providerPath = null;
if (config.injectProvider){
try (ZipFile sourceFile = new ZipFile(sourceFileaa)) {
providerPath = Paths.get(appInfo.dataDir, "cache/npatch/origin/p_" + sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc()+".dex");
Files.deleteIfExists(providerPath);
try (InputStream is = baseClassLoader.getResourceAsStream(PROVIDER_DEX_ASSET_PATH)) {
Files.copy(is, providerPath);
}
}catch (Exception e){
Log.e(TAG, "Failed to inject provider:" + Log.getStackTraceString(e));
}
}
cacheApkPath.toFile().setWritable(false); cacheApkPath.toFile().setWritable(false);
@ -170,6 +187,24 @@ public class LSPApplication {
mPackages.remove(appInfo.packageName); mPackages.remove(appInfo.packageName);
appLoadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo); appLoadedApk = activityThread.getPackageInfoNoCheck(appInfo, compatInfo);
if (config.injectProvider){
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);
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); XposedHelpers.setObjectField(mBoundApplication, "info", appLoadedApk);
var activityClientRecordClass = XposedHelpers.findClass("android.app.ActivityThread$ActivityClientRecord", ActivityThread.class.getClassLoader()); var activityClientRecordClass = XposedHelpers.findClass("android.app.ActivityThread$ActivityClientRecord", ActivityThread.class.getClassLoader());

View File

@ -46,6 +46,7 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
@ -79,6 +80,9 @@ public class LSPatch {
@Parameter(names = {"-f", "--force"}, description = "Force overwrite exists output file") @Parameter(names = {"-f", "--force"}, description = "Force overwrite exists output file")
private boolean forceOverwrite = false; private boolean forceOverwrite = false;
@Parameter(names = {"-p", "--newpackage"}, description = "Patch with new package")
private String newPackageName = "";
@Parameter(names = {"-d", "--debuggable"}, description = "Set app to be debuggable") @Parameter(names = {"-d", "--debuggable"}, description = "Set app to be debuggable")
private boolean debuggableFlag = false; private boolean debuggableFlag = false;
@ -88,6 +92,9 @@ public class LSPatch {
@Parameter(names = {"--injectdex"}, description = "Inject directly the loder dex file into the original application package") @Parameter(names = {"--injectdex"}, description = "Inject directly the loder dex file into the original application package")
private boolean injectDex = false; private boolean injectDex = false;
@Parameter(names = {"--provider"}, description = "Inject Provider to manager data files")
private boolean isInjectProvider = 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;
@ -106,8 +113,7 @@ public class LSPatch {
@Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk") @Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk")
private List<String> modules = new ArrayList<>(); private List<String> modules = new ArrayList<>();
@Parameter(names = {"-p", "--newpackage"}, description = "Patch with new package") private String packageName;
private String newPackageName = "";
private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml";
private static final HashSet<String> ARCHES = new HashSet<>(Arrays.asList( private static final HashSet<String> ARCHES = new HashSet<>(Arrays.asList(
@ -247,6 +253,7 @@ public class LSPatch {
throw new PatchError("Failed to parse AndroidManifest.xml"); throw new PatchError("Failed to parse AndroidManifest.xml");
appComponentFactory = pair.appComponentFactory; appComponentFactory = pair.appComponentFactory;
minSdkVersion = pair.minSdkVersion; minSdkVersion = pair.minSdkVersion;
packageName = pair.packageName;
logger.d("original appComponentFactory class: " + appComponentFactory); logger.d("original appComponentFactory class: " + appComponentFactory);
logger.d("original minSdkVersion: " + minSdkVersion); logger.d("original minSdkVersion: " + minSdkVersion);
@ -278,7 +285,7 @@ public class LSPatch {
logger.i("Patching apk..."); logger.i("Patching apk...");
// modify manifest // modify manifest
final var config = new PatchConfig(useManager, debuggableFlag, overrideVersionCode, sigbypassLevel, originalSignature, appComponentFactory, outputLog, newPackage); 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 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, pair.permissions, pair.use_permissions, pair.authorities))) { try (var is = new ByteArrayInputStream(modifyManifestFile(manifestEntry.open(), metadata, minSdkVersion, pair.packageName, newPackage, pair.permissions, pair.use_permissions, pair.authorities))) {
@ -310,6 +317,14 @@ public class LSPatch {
throw new PatchError("Error when adding dex", e); throw new PatchError("Error when adding dex", e);
} }
if (isInjectProvider){
try (var is = getClass().getClassLoader().getResourceAsStream("assets/provider.dex")) {
dstZFile.add("assets/lspatch/provider.dex", is);
} catch (Throwable e) {
throw new PatchError("Error when adding dex", e);
}
}
if (!useManager) { if (!useManager) {
logger.i("Adding loader dex..."); logger.i("Adding loader dex...");
try (var is = getClass().getClassLoader().getResourceAsStream(LOADER_DEX_ASSET_PATH)) { try (var is = getClass().getClassLoader().getResourceAsStream(LOADER_DEX_ASSET_PATH)) {
@ -464,6 +479,17 @@ public class LSPatch {
// 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");
if (isInjectProvider){
HashMap<String,String> providerMap = new HashMap<>();
providerMap.put("name","bin.mt.file.content.MTDataFilesProvider");
providerMap.put("permission","android.permission.MANAGE_DOCUMENTS");
providerMap.put("exported","true");
providerMap.put("authorities",packageName + ".MTDataFilesProvider");
providerMap.put("grantUriPermissions","true");
property.addProvider(providerMap,"android.content.action.DOCUMENTS_PROVIDER");
}
var os = new ByteArrayOutputStream(); var os = new ByteArrayOutputStream();
(new ManifestEditor(is, os, property)).processManifest(); (new ManifestEditor(is, os, property)).processManifest();

View File

@ -5,13 +5,14 @@ public class Constants {
final static public String CONFIG_ASSET_PATH = "assets/lspatch/config.json"; final static public String CONFIG_ASSET_PATH = "assets/lspatch/config.json";
final static public String LOADER_DEX_ASSET_PATH = "assets/lspatch/loader.dex"; final static public String LOADER_DEX_ASSET_PATH = "assets/lspatch/loader.dex";
final static public String META_LOADER_DEX_ASSET_PATH = "assets/lspatch/metaloader.dex"; final static public String META_LOADER_DEX_ASSET_PATH = "assets/lspatch/metaloader.dex";
final static public String PROVIDER_DEX_ASSET_PATH = "assets/lspatch/provider.dex";
final static public String ORIGINAL_APK_ASSET_PATH = "assets/lspatch/origin.apk"; final static public String ORIGINAL_APK_ASSET_PATH = "assets/lspatch/origin.apk";
final static public String EMBEDDED_MODULES_ASSET_PATH = "assets/lspatch/modules/"; final static public String EMBEDDED_MODULES_ASSET_PATH = "assets/lspatch/modules/";
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.lspatch.metaloader.LSPAppComponentFactoryStub"; final static public String PROXY_APP_COMPONENT_FACTORY = "org.lsposed.lspatch.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 int MIN_ROLLING_VERSION_CODE = 348; final static public int MIN_ROLLING_VERSION_CODE = 335;
final static public int SIGBYPASS_LV_DISABLE = 0; final static public int SIGBYPASS_LV_DISABLE = 0;
final static public int SIGBYPASS_LV_PM = 1; final static public int SIGBYPASS_LV_PM = 1;

View File

@ -5,6 +5,7 @@ public class PatchConfig {
public final boolean useManager; public final boolean useManager;
public final boolean debuggable; public final boolean debuggable;
public final boolean overrideVersionCode; public final boolean overrideVersionCode;
public final boolean injectProvider;
public final boolean outputLog; public final boolean outputLog;
public final int sigBypassLevel; public final int sigBypassLevel;
public final String originalSignature; public final String originalSignature;
@ -20,6 +21,7 @@ public class PatchConfig {
int sigBypassLevel, int sigBypassLevel,
String originalSignature, String originalSignature,
String appComponentFactory, String appComponentFactory,
boolean injectProvider,
boolean outputLog, boolean outputLog,
String newPackage String newPackage
) { ) {
@ -30,6 +32,7 @@ public class PatchConfig {
this.originalSignature = originalSignature; this.originalSignature = originalSignature;
this.appComponentFactory = appComponentFactory; this.appComponentFactory = appComponentFactory;
this.lspConfig = LSPConfig.instance; this.lspConfig = LSPConfig.instance;
this.injectProvider = injectProvider;
this.managerPackageName = Constants.MANAGER_PACKAGE_NAME; this.managerPackageName = Constants.MANAGER_PACKAGE_NAME;
this.newPackage = newPackage; this.newPackage = newPackage;
this.outputLog = outputLog; this.outputLog = outputLog;