From de06c1cf7cfb1c0bfbee19edb1a60645806573dd Mon Sep 17 00:00:00 2001 From: Nullptr Date: Tue, 14 Sep 2021 00:41:40 +0800 Subject: [PATCH] Fix Android 9 compatibility --- app/src/main/AndroidManifest.xml | 2 +- .../lspatch/loader/LSPApplication.java | 54 ++++++++++- .../lspatch/loader/util/FileUtils.java | 46 +++++++++- appstub/build.gradle | 1 - .../appstub/LSPAppComponentFactoryStub.java | 89 +++++++------------ .../lspatch/appstub/LSPApplicationStub.java | 41 --------- build.gradle | 2 +- core | 2 +- .../org/lsposed/lspatch/share/FileUtils.java | 50 ----------- 9 files changed, 131 insertions(+), 156 deletions(-) delete mode 100644 appstub/src/main/java/org/lsposed/lspatch/appstub/LSPApplicationStub.java delete mode 100644 share/src/main/java/org/lsposed/lspatch/share/FileUtils.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 81d756b..069c784 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,8 +6,8 @@ stubClass = Class.forName("org.lsposed.lspatch.appstub.LSPAppComponentFactoryStub", false, baseClassLoader); + + String originPath = aInfo.dataDir + "/cache/lspatch/origin/"; + String originalAppComponentFactoryClass = FileUtils.readTextFromInputStream(baseClassLoader.getResourceAsStream(ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH)); + String cacheApkPath; + try (ZipFile sourceFile = new ZipFile(aInfo.sourceDir)) { + cacheApkPath = originPath + sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc(); + } + if (!Files.exists(Paths.get(cacheApkPath))) { + Log.i(TAG, "extract original apk"); + FileUtils.deleteFolderIfExists(Paths.get(originPath)); + Files.createDirectories(Paths.get(originPath)); + try (InputStream is = baseClassLoader.getResourceAsStream(ORIGINAL_APK_ASSET_PATH)) { + Files.copy(is, Paths.get(cacheApkPath)); + } + } + var appClassLoader = new DelegateLastClassLoader(cacheApkPath, aInfo.nativeLibraryDir, baseClassLoader.getParent()); + AppComponentFactory originalAppComponentFactory; + try { + originalAppComponentFactory = (AppComponentFactory) appClassLoader.loadClass(originalAppComponentFactoryClass).newInstance(); + } catch (ClassNotFoundException | NullPointerException ignored) { + if (originalAppComponentFactoryClass != null && !originalAppComponentFactoryClass.isEmpty()) + Log.w(TAG, "original AppComponentFactory not found"); + originalAppComponentFactory = new AppComponentFactory(); + } + Field mClassLoaderField = LoadedApk.class.getDeclaredField("mClassLoader"); + mClassLoaderField.setAccessible(true); + mClassLoaderField.set(loadedApkObj, appClassLoader); + + stubClass.getDeclaredField("appClassLoader").set(null, appClassLoader); + stubClass.getDeclaredField("originalAppComponentFactory").set(null, originalAppComponentFactory); + + Log.d(TAG, "set up original AppComponentFactory"); + } catch (Throwable e) { + Log.e(TAG, "initAppComponentFactory", e); + } + } + public static void disableProfile(Context context) { final ArrayList codePaths = new ArrayList<>(); var appInfo = context.getApplicationInfo(); @@ -182,7 +232,7 @@ public class LSPApplication extends ApplicationServiceClient { if (!Files.exists(Paths.get(cacheApkPath))) { Log.i(TAG, "extract module apk: " + packageName); - org.lsposed.lspatch.share.FileUtils.deleteFolderIfExists(Paths.get(modulePath)); + FileUtils.deleteFolderIfExists(Paths.get(modulePath)); Files.createDirectories(Paths.get(modulePath)); try (var is = context.getAssets().open("modules/" + name)) { Files.copy(is, Paths.get(cacheApkPath)); @@ -331,7 +381,7 @@ public class LSPApplication extends ApplicationServiceClient { } Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info infoField.setAccessible(true); - LoadedApk loadedApkObj = (LoadedApk) infoField.get(mBoundApplication); // LoadedApk + loadedApkObj = (LoadedApk) infoField.get(mBoundApplication); // LoadedApk if (loadedApkObj == null) { Log.e(TAG, "loadedApkObj null"); return null; diff --git a/app/src/main/java/org/lsposed/lspatch/loader/util/FileUtils.java b/app/src/main/java/org/lsposed/lspatch/loader/util/FileUtils.java index 1b40415..d374a6c 100644 --- a/app/src/main/java/org/lsposed/lspatch/loader/util/FileUtils.java +++ b/app/src/main/java/org/lsposed/lspatch/loader/util/FileUtils.java @@ -2,16 +2,60 @@ package org.lsposed.lspatch.loader.util; import android.content.Context; +import java.io.BufferedReader; +import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; public class FileUtils { + public static String readTextFromInputStream(InputStream is) { + try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader(reader)) { + StringBuilder builder = new StringBuilder(); + String str; + while ((str = bufferedReader.readLine()) != null) { + builder.append(str); + } + return builder.toString(); + } catch (Throwable ignored) { + } + return null; + } + + public static void deleteFolderIfExists(Path target) throws IOException { + if (Files.notExists(target)) return; + Files.walkFileTree(target, new SimpleFileVisitor<>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) + throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException e) + throws IOException { + if (e == null) { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } else { + throw e; + } + } + }); + } public static String readTextFromAssets(Context context, String assetsFileName) { if (context == null) { throw new IllegalStateException("context null"); } try (InputStream is = context.getAssets().open(assetsFileName)) { - return org.lsposed.lspatch.share.FileUtils.readTextFromInputStream(is); + return readTextFromInputStream(is); } catch (Throwable ignored) { } return null; diff --git a/appstub/build.gradle b/appstub/build.gradle index db21a89..b216341 100644 --- a/appstub/build.gradle +++ b/appstub/build.gradle @@ -50,7 +50,6 @@ android { } dependencies { - implementation project(':share') implementation 'de.upb.cs.swt:axml:2.1.1' compileOnly project(":hiddenapi-stubs") } diff --git a/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPAppComponentFactoryStub.java b/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPAppComponentFactoryStub.java index 7b4a8e1..df41c30 100644 --- a/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPAppComponentFactoryStub.java +++ b/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPAppComponentFactoryStub.java @@ -1,8 +1,5 @@ package org.lsposed.lspatch.appstub; -import static org.lsposed.lspatch.share.Constants.ORIGINAL_APK_ASSET_PATH; -import static org.lsposed.lspatch.share.Constants.ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH; - import android.annotation.SuppressLint; import android.app.Activity; import android.app.AppComponentFactory; @@ -11,78 +8,54 @@ import android.app.Service; import android.content.BroadcastReceiver; import android.content.ContentProvider; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.util.Log; -import org.lsposed.lspatch.share.FileUtils; +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Method; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.zip.ZipFile; - -import dalvik.system.DelegateLastClassLoader; - -@SuppressLint("NewApi") +@SuppressLint({"UnsafeDynamicallyLoadedCode", "DiscouragedPrivateApi"}) public class LSPAppComponentFactoryStub extends AppComponentFactory { private static final String TAG = "LSPatch"; - private static final String PROXY_APPLICATION = "org.lsposed.lspatch.appstub.LSPApplicationStub"; - private ClassLoader appClassLoader = null; - private ClassLoader baseClassLoader = null; - private AppComponentFactory originalAppComponentFactory = null; + public static byte[] dex = null; + public static ClassLoader appClassLoader = null; + public static ClassLoader baseClassLoader = null; + public static AppComponentFactory originalAppComponentFactory = null; - /** - * Instantiate original AppComponentFactory
- * This method will be called at instantiateClassLoader by createOrUpdateClassLoaderLocked - **/ - private void initOriginalAppComponentFactory(ApplicationInfo aInfo) { - final String originPath = aInfo.dataDir + "/cache/lspatch/origin/"; - final String originalAppComponentFactoryClass = FileUtils.readTextFromInputStream(baseClassLoader.getResourceAsStream(ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH)); + public LSPAppComponentFactoryStub() { + baseClassLoader = getClass().getClassLoader(); + try (var is = baseClassLoader.getResourceAsStream("assets/lsp"); + var os = new ByteArrayOutputStream()) { + byte[] buffer = new byte[8192]; + int n; + while (-1 != (n = is.read(buffer))) { + os.write(buffer, 0, n); + } + dex = os.toByteArray(); + } catch (Throwable e) { + Log.e("LSPatch", "load dex error", e); + } try { - String cacheApkPath; - try (ZipFile sourceFile = new ZipFile(aInfo.sourceDir)) { - cacheApkPath = originPath + sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc(); - } + Class VMRuntime = Class.forName("dalvik.system.VMRuntime"); + Method getRuntime = VMRuntime.getDeclaredMethod("getRuntime"); + getRuntime.setAccessible(true); + Method vmInstructionSet = VMRuntime.getDeclaredMethod("vmInstructionSet"); + vmInstructionSet.setAccessible(true); - if (!Files.exists(Paths.get(cacheApkPath))) { - Log.i(TAG, "extract original apk"); - FileUtils.deleteFolderIfExists(Paths.get(originPath)); - Files.createDirectories(Paths.get(originPath)); - try (InputStream is = baseClassLoader.getResourceAsStream(ORIGINAL_APK_ASSET_PATH)) { - Files.copy(is, Paths.get(cacheApkPath)); - } - } - - appClassLoader = new DelegateLastClassLoader(cacheApkPath, aInfo.nativeLibraryDir, baseClassLoader); - - try { - originalAppComponentFactory = (AppComponentFactory) appClassLoader.loadClass(originalAppComponentFactoryClass).newInstance(); - } catch (ClassNotFoundException | NullPointerException ignored) { - if (originalAppComponentFactoryClass != null && !originalAppComponentFactoryClass.isEmpty()) - Log.w(TAG, "original AppComponentFactory not found"); - originalAppComponentFactory = new AppComponentFactory(); - } - - Log.d(TAG, "instantiate original AppComponentFactory: " + originalAppComponentFactory); + String arch = (String) vmInstructionSet.invoke(getRuntime.invoke(null)); + String path = baseClassLoader.getResource("assets/lib/lspd/" + arch + "/liblspd.so").getPath().substring(5); + System.load(path); } catch (Throwable e) { - Log.e(TAG, "initOriginalAppComponentFactory", e); + Log.e("LSPatch", "load lspd error", e); } } @Override - public ClassLoader instantiateClassLoader(ClassLoader cl, ApplicationInfo aInfo) { - baseClassLoader = cl; - initOriginalAppComponentFactory(aInfo); + public Application instantiateApplication(ClassLoader cl, String className) throws IllegalAccessException, InstantiationException, ClassNotFoundException { Log.d(TAG, "baseClassLoader is " + baseClassLoader); Log.d(TAG, "appClassLoader is " + appClassLoader); - return originalAppComponentFactory.instantiateClassLoader(appClassLoader, aInfo); - } - - @Override - public Application instantiateApplication(ClassLoader cl, String className) throws IllegalAccessException, InstantiationException, ClassNotFoundException { - baseClassLoader.loadClass(PROXY_APPLICATION).newInstance(); + Log.d(TAG, "originalAppComponentFactory is " + originalAppComponentFactory); Log.i(TAG, "lspd initialized, instantiate original application"); return originalAppComponentFactory.instantiateApplication(cl, className); } diff --git a/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPApplicationStub.java b/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPApplicationStub.java deleted file mode 100644 index 84f580e..0000000 --- a/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPApplicationStub.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.lsposed.lspatch.appstub; - -import android.annotation.SuppressLint; -import android.util.Log; - -import java.io.ByteArrayOutputStream; -import java.lang.reflect.Method; - -@SuppressLint("UnsafeDynamicallyLoadedCode") -public class LSPApplicationStub { - - private static byte[] dex = null; - - static { - try (var is = LSPApplicationStub.class.getClassLoader().getResourceAsStream("assets/lsp"); - var os = new ByteArrayOutputStream()) { - byte[] buffer = new byte[8192]; - int n; - while (-1 != (n = is.read(buffer))) { - os.write(buffer, 0, n); - } - dex = os.toByteArray(); - } catch (Throwable e) { - Log.e("LSPatch", "load dex error", e); - } - - try { - Class VMRuntime = Class.forName("dalvik.system.VMRuntime"); - Method getRuntime = VMRuntime.getDeclaredMethod("getRuntime"); - getRuntime.setAccessible(true); - Method vmInstructionSet = VMRuntime.getDeclaredMethod("vmInstructionSet"); - vmInstructionSet.setAccessible(true); - - String arch = (String) vmInstructionSet.invoke(getRuntime.invoke(null)); - String path = LSPApplicationStub.class.getClassLoader().getResource("assets/lib/lspd/" + arch + "/liblspd.so").getPath().substring(5); - System.load(path); - } catch (Throwable e) { - Log.e("LSPatch", "load lspd error", e); - } - } -} diff --git a/build.gradle b/build.gradle index 061482f..4229e98 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ ext { androidCompileSdkVersion = 31 androidCompileNdkVersion = "23.0.7599858" androidBuildToolsVersion = "31.0.0" - androidMinSdkVersion = 27 + androidMinSdkVersion = 28 androidTargetSdkVersion = 30 verCode = 1 verName = "lspatch" diff --git a/core b/core index 89eeddd..a6be001 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 89eedddbd94af1240c14e6172e98c19bd4c7cafa +Subproject commit a6be0018e02bbd0d581c8a5f7ee9310bb8a6a711 diff --git a/share/src/main/java/org/lsposed/lspatch/share/FileUtils.java b/share/src/main/java/org/lsposed/lspatch/share/FileUtils.java deleted file mode 100644 index 5d14ba4..0000000 --- a/share/src/main/java/org/lsposed/lspatch/share/FileUtils.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.lsposed.lspatch.share; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; - -public class FileUtils { - public static String readTextFromInputStream(InputStream is) { - try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader(reader)) { - StringBuilder builder = new StringBuilder(); - String str; - while ((str = bufferedReader.readLine()) != null) { - builder.append(str); - } - return builder.toString(); - } catch (Throwable ignored) { - } - return null; - } - - public static void deleteFolderIfExists(Path target) throws IOException { - if (Files.notExists(target)) return; - Files.walkFileTree(target, new SimpleFileVisitor<>() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) - throws IOException { - Files.delete(file); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException e) - throws IOException { - if (e == null) { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } else { - throw e; - } - } - }); - } -} \ No newline at end of file