diff --git a/app/build.gradle b/app/build.gradle index 3e9a85d..4dc2e4c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -84,4 +84,5 @@ dependencies { implementation project(path: ':hiddenapi-bridge') compileOnly project(":hiddenapi-stubs") implementation project(':share') + implementation project(':imanager') } diff --git a/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java b/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java index df9282b..b181800 100644 --- a/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java +++ b/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java @@ -8,21 +8,19 @@ import android.app.LoadedApk; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.IBinder; import android.os.Parcel; +import android.os.RemoteException; import android.os.SharedMemory; import android.system.ErrnoException; import android.system.Os; import android.system.OsConstants; import android.util.Log; -import org.json.JSONArray; -import org.json.JSONObject; import org.lsposed.lspatch.loader.util.FileUtils; import org.lsposed.lspatch.loader.util.XLog; import org.lsposed.lspatch.share.Constants; @@ -33,9 +31,7 @@ import org.lsposed.lspd.models.PreLoadedApk; import org.lsposed.lspd.nativebridge.SigBypass; import java.io.BufferedReader; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -44,15 +40,13 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.nio.channels.Channels; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.zip.ZipFile; import de.robv.android.xposed.XC_MethodHook; @@ -67,11 +61,14 @@ import hidden.HiddenApiBridge; public class LSPApplication extends ApplicationServiceClient { private static final String ORIGINAL_APPLICATION_NAME_ASSET_PATH = "original_application_name.ini"; private static final String ORIGINAL_SIGNATURE_ASSET_PATH = "original_signature_info.ini"; + private static final String USE_MANAGER_CONTROL_PATH = "use_manager.ini"; private static final String TAG = "LSPatch"; + private static boolean useManager; private static String originalApplicationName = null; private static String originalSignature = null; private static Application sOriginalApplication = null; + private static ManagerResolver managerResolver = null; private static ClassLoader appClassLoader; private static Object activityThread; @@ -99,9 +96,16 @@ public class LSPApplication extends ApplicationServiceClient { return; } + useManager = Boolean.parseBoolean(Objects.requireNonNull(FileUtils.readTextFromAssets(context, USE_MANAGER_CONTROL_PATH))); originalApplicationName = FileUtils.readTextFromAssets(context, ORIGINAL_APPLICATION_NAME_ASSET_PATH); originalSignature = FileUtils.readTextFromAssets(context, ORIGINAL_SIGNATURE_ASSET_PATH); + if (useManager) try { + managerResolver = new ManagerResolver(context); + } catch (RemoteException e) { + Log.e(TAG, "Failed to instantiate manager resolver", e); + } + XLog.d(TAG, "original application class " + originalApplicationName); XLog.d(TAG, "original signature info " + originalSignature); @@ -169,98 +173,40 @@ public class LSPApplication extends ApplicationServiceClient { } - - // TODO: set module config public static void loadModules(Context context) { - var configFile = new File(context.getExternalFilesDir(null), "lspatch.json"); - JSONObject moduleConfigs = new JSONObject(); - try (var is = new FileInputStream(configFile)) { - moduleConfigs = new JSONObject(FileUtils.readTextFromInputStream(is)); - } catch (Throwable ignored) { - } - var modules = moduleConfigs.optJSONArray("modules"); - if (modules == null) { - modules = new JSONArray(); + if (useManager) { try { - moduleConfigs.put("modules", modules); - } catch (Throwable ignored) { - + LSPApplication.modules.addAll(managerResolver.getModules()); + } catch (NullPointerException | RemoteException e) { + Log.e(TAG, "Failed to get modules from manager", e); } - } - HashSet embedded_modules = new HashSet<>(); - HashSet disabled_modules = new HashSet<>(); - try { - for (var name : context.getAssets().list("modules")) { - String packageName = name.substring(0, name.length() - 4); - String modulePath = context.getCacheDir() + "/lspatch/" + packageName + "/"; - String cacheApkPath; - try (ZipFile sourceFile = new ZipFile(context.getApplicationInfo().sourceDir)) { - cacheApkPath = modulePath + sourceFile.getEntry("assets/modules/" + name).getCrc(); - } - - if (!Files.exists(Paths.get(cacheApkPath))) { - Log.i(TAG, "extract module apk: " + packageName); - FileUtils.deleteFolderIfExists(Paths.get(modulePath)); - Files.createDirectories(Paths.get(modulePath)); - try (var is = context.getAssets().open("modules/" + name)) { - Files.copy(is, Paths.get(cacheApkPath)); + } else { + try { + for (var name : context.getAssets().list("modules")) { + String packageName = name.substring(0, name.length() - 4); + String modulePath = context.getCacheDir() + "/lspatch/" + packageName + "/"; + String cacheApkPath; + try (ZipFile sourceFile = new ZipFile(context.getApplicationInfo().sourceDir)) { + cacheApkPath = modulePath + sourceFile.getEntry("assets/modules/" + name).getCrc(); } + + if (!Files.exists(Paths.get(cacheApkPath))) { + Log.i(TAG, "extract module apk: " + packageName); + FileUtils.deleteFolderIfExists(Paths.get(modulePath)); + Files.createDirectories(Paths.get(modulePath)); + try (var is = context.getAssets().open("modules/" + name)) { + Files.copy(is, Paths.get(cacheApkPath)); + } + } + + var module = new Module(); + module.apkPath = cacheApkPath; + module.packageName = packageName; + module.file = loadModule(context, cacheApkPath); + modules.add(module); } - - embedded_modules.add(packageName); - var module = new Module(); - module.apkPath = cacheApkPath; - module.packageName = packageName; - LSPApplication.modules.add(module); - } - } catch (Throwable ignored) { - - } - for (int i = 0; i < modules.length(); ++i) { - var module = modules.optJSONObject(i); - var name = module.optString("name"); - var enabled = module.optBoolean("enabled", true); - var useEmbed = module.optBoolean("use_embed", false); - if (name.isEmpty()) continue; - if (!enabled) disabled_modules.add(name); - if (embedded_modules.contains(name) && !useEmbed) embedded_modules.remove(name); - } - - for (PackageInfo pkg : context.getPackageManager().getInstalledPackages(PackageManager.GET_META_DATA)) { - ApplicationInfo app = pkg.applicationInfo; - if (!app.enabled) { - continue; - } - if (app.metaData != null && app.metaData.containsKey("xposedminversion") && !embedded_modules.contains(app.packageName)) { - var module = new Module(); - module.apkPath = app.publicSourceDir; - module.packageName = app.packageName; - LSPApplication.modules.add(module); - } - } - final var new_modules = new JSONArray(); - LSPApplication.modules.forEach(m -> { - try { - m.file = loadModule(context, m.apkPath); - var module = new JSONObject(); - module.put("name", m.packageName); - module.put("enabled", !disabled_modules.contains(m.packageName)); - module.put("use_embed", embedded_modules.contains(m.packageName)); - module.put("path", m.apkPath); - new_modules.put(module); } catch (Throwable ignored) { } - }); - try { - moduleConfigs.put("modules", new_modules); - } catch (Throwable ignored) { - } - try (var is = new ByteArrayInputStream(moduleConfigs.toString(4).getBytes(StandardCharsets.UTF_8))) { - Files.copy(is, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } catch (Throwable ignored) { - } - for (var module : disabled_modules) { - LSPApplication.modules.remove(module); } } diff --git a/app/src/main/java/org/lsposed/lspatch/loader/ManagerResolver.java b/app/src/main/java/org/lsposed/lspatch/loader/ManagerResolver.java new file mode 100644 index 0000000..4022dd4 --- /dev/null +++ b/app/src/main/java/org/lsposed/lspatch/loader/ManagerResolver.java @@ -0,0 +1,35 @@ +package org.lsposed.lspatch.loader; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; + +import org.lsposed.lspatch.manager.IManagerService; +import org.lsposed.lspd.models.Module; + +import java.util.List; + +public class ManagerResolver extends ContentResolver { + private static final String MANAGER_PACKAGE_NAME = "org.lsposed.lspatch"; + private static final Uri BINDER_URI = Uri.parse("content://" + MANAGER_PACKAGE_NAME + "/binder"); + + private final IManagerService service; + + public ManagerResolver(Context context) throws RemoteException { + super(context); + try { + Bundle back = call(BINDER_URI, "getBinder", null, null); + service = IManagerService.Stub.asInterface(back.getBinder("binder")); + } catch (Throwable t) { + var e = new RemoteException("Failed to get manager binder"); + e.addSuppressed(t); + throw e; + } + } + + public List getModules() throws RemoteException { + return service.getModules(); + } +} diff --git a/core b/core index a2e57dd..f955755 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit a2e57ddfe9477933e6540ce9dad4c1dac1991d32 +Subproject commit f955755ac40134f1b0a858f4e7df3b4a4eec78a8 diff --git a/imanager/.gitignore b/imanager/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/imanager/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/imanager/build.gradle b/imanager/build.gradle new file mode 100644 index 0000000..db596da --- /dev/null +++ b/imanager/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdk 31 + + defaultConfig { + minSdk 27 + targetSdk 31 + } + + buildTypes { + release { + minifyEnabled true + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +dependencies { + implementation project(path: ':lspcore') +} \ No newline at end of file diff --git a/imanager/src/main/AndroidManifest.xml b/imanager/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0a8ce9c --- /dev/null +++ b/imanager/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/imanager/src/main/aidl/org/lsposed/lspatch/manager/IManagerService.aidl b/imanager/src/main/aidl/org/lsposed/lspatch/manager/IManagerService.aidl new file mode 100644 index 0000000..25b3023 --- /dev/null +++ b/imanager/src/main/aidl/org/lsposed/lspatch/manager/IManagerService.aidl @@ -0,0 +1,7 @@ +package org.lsposed.lspatch.manager; + +import org.lsposed.lspd.models.Module; + +interface IManagerService { + List getModules(); +} \ No newline at end of file diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java index 060d104..57bd703 100644 --- a/patch/src/main/java/org/lsposed/patch/LSPatch.java +++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java @@ -78,6 +78,9 @@ public class LSPatch { @Parameter(names = {"--v3"}, arity = 1, description = "Sign with v3 signature") private boolean v3 = true; + @Parameter(names = {"--manager"}, arity = 1, description = "Whether use manager (Cannot be true when has module embedded)") + private boolean useManager = false; + @Parameter(names = {"-v", "--verbose"}, description = "Verbose output") private boolean verbose = false; @@ -90,6 +93,7 @@ public class LSPatch { private static final String APP_COMPONENT_FACTORY_ASSET_PATH = "assets/original_app_component_factory.ini"; private static final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini"; private static final String SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini"; + private static final String USE_MANAGER_CONTROL_PATH = "assets/use_manager.ini"; private static final String ORIGINAL_APK_ASSET_PATH = "assets/origin_apk.bin"; private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml"; private static final HashSet ARCHES = new HashSet<>(Arrays.asList( @@ -135,6 +139,11 @@ public class LSPatch { return; } + if (!modules.isEmpty() && useManager) { + jCommander.usage(); + return; + } + for (var apk : apkPaths) { File srcApkFile = new File(apk).getAbsoluteFile(); @@ -274,8 +283,9 @@ public class LSPatch { // save lspatch config to asset.. try (var is = new ByteArrayInputStream("42".getBytes(StandardCharsets.UTF_8))) { dstZFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, is); - } catch (Throwable e) { - throw new PatchError("Error when saving signature bypass level", e); + } + try (var is = new ByteArrayInputStream(Boolean.toString(useManager).getBytes(StandardCharsets.UTF_8))){ + dstZFile.add(USE_MANAGER_CONTROL_PATH, is); } Set apkArchs = new HashSet<>(); diff --git a/settings.gradle b/settings.gradle index f1cc8c9..98e80da 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,3 +15,4 @@ include ':axmlprinter' include ':share' include ':appstub' include ':apkzlib' +include ':imanager'