Support configuration
This commit is contained in:
parent
5cd3f73ed1
commit
ce39256f3c
|
|
@ -1,44 +1,58 @@
|
|||
package org.lsposed.lspatch.loader;
|
||||
|
||||
import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE;
|
||||
import static org.lsposed.lspatch.loader.LSPLoader.initAndLoadModules;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.Application;
|
||||
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.Environment;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
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.loader.util.XpatchUtils;
|
||||
import org.lsposed.lspatch.share.Constants;
|
||||
import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
|
||||
import org.lsposed.lspd.config.ApplicationServiceClient;
|
||||
import org.lsposed.lspd.core.Main;
|
||||
import org.lsposed.lspd.nativebridge.SigBypass;
|
||||
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import de.robv.android.xposed.XposedInit;
|
||||
|
||||
/**
|
||||
* Created by Windysha
|
||||
*/
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
public class LSPApplication {
|
||||
@SuppressWarnings("unused")
|
||||
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 TAG = "LSPatch";
|
||||
|
|
@ -56,26 +70,24 @@ public class LSPApplication {
|
|||
|
||||
static private LSPApplication instance = null;
|
||||
|
||||
static private final Map<String, String> modules = new HashMap<>();
|
||||
|
||||
static public boolean isIsolated() {
|
||||
return (android.os.Process.myUid() % PER_USER_RANGE) >= FIRST_APP_ZYGOTE_ISOLATED_UID;
|
||||
}
|
||||
|
||||
static public void onLoad() {
|
||||
public static void onLoad() {
|
||||
cacheSigbypassLv = -1;
|
||||
|
||||
if (isIsolated()) {
|
||||
XLog.d(TAG, "skip isolated process");
|
||||
return;
|
||||
}
|
||||
Context context = XpatchUtils.createAppContext();
|
||||
Context context = createAppContext();
|
||||
if (context == null) {
|
||||
XLog.e(TAG, "create context err");
|
||||
return;
|
||||
}
|
||||
YahfaHooker.init();
|
||||
// XposedBridge.initXResources();
|
||||
// PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
|
||||
XposedInit.startsSystemServer = false;
|
||||
|
||||
originalApplicationName = FileUtils.readTextFromAssets(context, ORIGINAL_APPLICATION_NAME_ASSET_PATH);
|
||||
originalSignature = FileUtils.readTextFromAssets(context, ORIGINAL_SIGNATURE_ASSET_PATH);
|
||||
|
|
@ -84,14 +96,107 @@ public class LSPApplication {
|
|||
XLog.d(TAG, "original signature info " + originalSignature);
|
||||
|
||||
instance = new LSPApplication();
|
||||
serviceClient = instance;
|
||||
|
||||
try {
|
||||
loadModules(context);
|
||||
Main.forkPostCommon(false, context.getDataDir().toString(), ActivityThread.currentProcessName());
|
||||
doHook(context);
|
||||
initAndLoadModules(context);
|
||||
// WARN: Since it uses `XResource`, the following class should not be initialized
|
||||
// before forkPostCommon is invoke. Otherwise, you will get failure of XResources
|
||||
LSPLoader.initModules(context);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Do hook", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void loadModules(Context context) {
|
||||
var configFile = new File(context.getExternalFilesDir(null), "lspatch.json");
|
||||
var cacheDir = new File(context.getExternalCacheDir(), "modules");
|
||||
cacheDir.mkdirs();
|
||||
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();
|
||||
try {
|
||||
moduleConfigs.put("modules", modules);
|
||||
} catch (Throwable ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
HashSet<String> embedded_modules = new HashSet<>();
|
||||
HashSet<String> disabled_modules = new HashSet<>();
|
||||
try {
|
||||
for (var name : context.getAssets().list("modules")) {
|
||||
var target = new File(cacheDir, name + ".apk");
|
||||
if (target.exists()) {
|
||||
embedded_modules.add(name);
|
||||
LSPApplication.modules.put(name, target.getAbsolutePath());
|
||||
continue;
|
||||
}
|
||||
try (var is = context.getAssets().open("modules/" + name)) {
|
||||
Files.copy(is, target.toPath());
|
||||
embedded_modules.add(name);
|
||||
LSPApplication.modules.put(name, target.getAbsolutePath());
|
||||
} catch (IOException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
var apk = module.optString("path");
|
||||
if (!enabled) disabled_modules.add(name);
|
||||
if (embedded_modules.contains(name) && useEmbed) continue;
|
||||
if (apk.isEmpty() || name.isEmpty()) continue;
|
||||
if (!new File(apk).exists()) continue;
|
||||
LSPApplication.modules.put(name, apk);
|
||||
}
|
||||
|
||||
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")) {
|
||||
LSPApplication.modules.computeIfAbsent(app.packageName, k -> app.publicSourceDir);
|
||||
}
|
||||
}
|
||||
final var new_modules = new JSONArray();
|
||||
LSPApplication.modules.forEach((k, v) -> {
|
||||
try {
|
||||
var module = new JSONObject();
|
||||
module.put("name", k);
|
||||
module.put("enabled", !disabled_modules.contains(k));
|
||||
module.put("use_embed", embedded_modules.contains(k));
|
||||
module.put("path", v);
|
||||
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);
|
||||
}
|
||||
Log.e(TAG, "DONE LOAD");
|
||||
}
|
||||
|
||||
public LSPApplication() {
|
||||
super();
|
||||
|
||||
|
|
@ -331,8 +436,7 @@ public class LSPApplication {
|
|||
private static Object getActivityThread() {
|
||||
if (activityThread == null) {
|
||||
try {
|
||||
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||
activityThread = XposedHelpers.callStaticMethod(activityThreadClass, "currentActivityThread");
|
||||
activityThread = ActivityThread.currentActivityThread();
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "getActivityThread", e);
|
||||
}
|
||||
|
|
@ -422,4 +526,78 @@ public class LSPApplication {
|
|||
Log.e(TAG, "modifyApplicationInfoClassName", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Context createAppContext() {
|
||||
try {
|
||||
|
||||
ActivityThread activityThreadObj = ActivityThread.currentActivityThread();
|
||||
|
||||
Field boundApplicationField = ActivityThread.class.getDeclaredField("mBoundApplication");
|
||||
boundApplicationField.setAccessible(true);
|
||||
Object mBoundApplication = boundApplicationField.get(activityThreadObj); // AppBindData
|
||||
if (mBoundApplication == null) {
|
||||
Log.e(TAG, "mBoundApplication null");
|
||||
return null;
|
||||
}
|
||||
Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info
|
||||
infoField.setAccessible(true);
|
||||
LoadedApk loadedApkObj = (LoadedApk) infoField.get(mBoundApplication); // LoadedApk
|
||||
if (loadedApkObj == null) {
|
||||
Log.e(TAG, "loadedApkObj null");
|
||||
return null;
|
||||
}
|
||||
Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
|
||||
Method createAppContextMethod = contextImplClass.getDeclaredMethod("createAppContext", ActivityThread.class, LoadedApk.class);
|
||||
createAppContextMethod.setAccessible(true);
|
||||
|
||||
Object context = createAppContextMethod.invoke(null, activityThreadObj, loadedApkObj);
|
||||
|
||||
if (context instanceof Context) {
|
||||
return (Context) context;
|
||||
}
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
|
||||
Log.e(TAG, "Fail to create app context", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder requestModuleBinder() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder requestManagerBinder(String packageName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isResourcesHookEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map getModulesList(String processName) {
|
||||
return getModulesList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getModulesList() {
|
||||
return modules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefsPath(String packageName) {
|
||||
return new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/").getAbsolutePath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor getModuleLogger() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder asBinder() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,324 +1,24 @@
|
|||
package org.lsposed.lspatch.loader;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityThread;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.content.res.XResources;
|
||||
|
||||
import org.lsposed.lspatch.loader.util.FileUtils;
|
||||
import org.lsposed.lspatch.loader.util.XLog;
|
||||
import org.lsposed.lspatch.loader.util.XpatchUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import dalvik.system.DelegateLastClassLoader;
|
||||
import de.robv.android.xposed.IXposedHookInitPackageResources;
|
||||
import de.robv.android.xposed.IXposedHookLoadPackage;
|
||||
import de.robv.android.xposed.IXposedHookZygoteInit;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelper;
|
||||
import de.robv.android.xposed.XposedInit;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
|
||||
public class LSPLoader {
|
||||
|
||||
private static final String TAG = LSPLoader.class.getSimpleName();
|
||||
private static final String DIR_BASE = Environment.getExternalStorageDirectory().getAbsolutePath();
|
||||
private static final String XPOSED_MODULE_FILE_PATH = "xpmodules.list";
|
||||
private static AtomicBoolean hasInited = new AtomicBoolean(false);
|
||||
private static Context appContext;
|
||||
|
||||
|
||||
@SuppressLint("DiscouragedPrivateApi")
|
||||
public static boolean loadModule(final String moduleApkPath, String moduleLibPath, final ApplicationInfo currentApplicationInfo, ClassLoader appClassLoader) {
|
||||
|
||||
XLog.i(TAG, "Loading modules from " + moduleApkPath);
|
||||
|
||||
if (!new File(moduleApkPath).exists()) {
|
||||
XLog.e(TAG, moduleApkPath + " does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
// module can load it's own so
|
||||
StringBuilder nativePath = new StringBuilder();
|
||||
for (String i : Build.SUPPORTED_ABIS) {
|
||||
nativePath.append(moduleApkPath).append("!/lib/").append(i).append(File.pathSeparator);
|
||||
}
|
||||
ClassLoader initLoader = XposedInit.class.getClassLoader();
|
||||
ClassLoader mcl = new DelegateLastClassLoader(moduleApkPath, nativePath.toString(), initLoader);
|
||||
|
||||
try {
|
||||
if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) {
|
||||
Log.e(TAG, "Cannot load module:");
|
||||
Log.e(TAG, "The Xposed API classes are compiled into the module's APK.");
|
||||
Log.e(TAG, "This may cause strange issues and must be fixed by the module developer.");
|
||||
Log.e(TAG, "For details, see: http://api.xposed.info/using.html");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException ignored) {
|
||||
}
|
||||
|
||||
try (InputStream is = mcl.getResourceAsStream("assets/xposed_init")) {
|
||||
if (is == null) {
|
||||
XLog.e(TAG, "assets/xposed_init not found in the APK");
|
||||
return false;
|
||||
}
|
||||
|
||||
BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
|
||||
String moduleClassName;
|
||||
while ((moduleClassName = moduleClassesReader.readLine()) != null) {
|
||||
moduleClassName = moduleClassName.trim();
|
||||
if (moduleClassName.isEmpty() || moduleClassName.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
XLog.i(TAG, "Loading class " + moduleClassName);
|
||||
Class<?> moduleClass = mcl.loadClass(moduleClassName);
|
||||
|
||||
if (!XposedHelper.isIXposedMod(moduleClass)) {
|
||||
Log.w(TAG, "This class doesn't implement any sub-interface of IXposedMod, skipping it");
|
||||
continue;
|
||||
}
|
||||
else if (IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
|
||||
Log.w(TAG, "This class requires resource-related hooks (which are disabled), skipping it.");
|
||||
continue;
|
||||
}
|
||||
|
||||
final Object moduleInstance = moduleClass.newInstance();
|
||||
if (moduleInstance instanceof IXposedHookZygoteInit) {
|
||||
XposedHelper.callInitZygote(moduleApkPath, moduleInstance);
|
||||
}
|
||||
|
||||
if (moduleInstance instanceof IXposedHookLoadPackage) {
|
||||
// hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
|
||||
IXposedHookLoadPackage.Wrapper wrapper = new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance, moduleApkPath);
|
||||
XposedBridge.CopyOnWriteSortedSet<XC_LoadPackage> xc_loadPackageCopyOnWriteSortedSet = new XposedBridge.CopyOnWriteSortedSet<>();
|
||||
xc_loadPackageCopyOnWriteSortedSet.add(wrapper);
|
||||
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(xc_loadPackageCopyOnWriteSortedSet);
|
||||
lpparam.packageName = currentApplicationInfo.packageName;
|
||||
lpparam.processName = (String) Class.forName("android.app.ActivityThread").getDeclaredMethod("currentProcessName").invoke(null);
|
||||
lpparam.classLoader = appClassLoader;
|
||||
lpparam.appInfo = currentApplicationInfo;
|
||||
lpparam.isFirstApplication = true;
|
||||
XC_LoadPackage.callAll(lpparam);
|
||||
}
|
||||
|
||||
if (moduleInstance instanceof IXposedHookInitPackageResources) {
|
||||
XLog.e(TAG, "Unsupport resource hook");
|
||||
}
|
||||
}
|
||||
catch (Throwable t) {
|
||||
XLog.e(TAG, "", t);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
XLog.e(TAG, "", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void initAndLoadModules() {
|
||||
Context context = XpatchUtils.createAppContext();
|
||||
initAndLoadModules(context);
|
||||
}
|
||||
|
||||
public static void initAndLoadModules(Context context) {
|
||||
if (!hasInited.compareAndSet(false, true)) {
|
||||
XLog.w(TAG, "Has been init");
|
||||
return;
|
||||
}
|
||||
|
||||
if (context == null) {
|
||||
XLog.e(TAG, "Try to init with context null");
|
||||
return;
|
||||
}
|
||||
|
||||
appContext = context;
|
||||
|
||||
initSELinux(context);
|
||||
|
||||
ClassLoader originClassLoader = context.getClassLoader();
|
||||
List<String> modulePathList = loadAllInstalledModule(context);
|
||||
|
||||
for (String modulePath : modulePathList) {
|
||||
if (!TextUtils.isEmpty(modulePath)) {
|
||||
LSPLoader.loadModule(modulePath, null, context.getApplicationInfo(), originClassLoader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void initSELinux(Context context) {
|
||||
XposedHelper.initSeLinux(context.getApplicationInfo().processName);
|
||||
}
|
||||
|
||||
private static List<String> loadAllInstalledModule(Context context) {
|
||||
PackageManager pm = context.getPackageManager();
|
||||
List<String> modulePathList = new ArrayList<>();
|
||||
|
||||
List<String> packageNameList = loadPackageNameListFromFile(true);
|
||||
List<Pair<String, String>> installedModuleList = new ArrayList<>();
|
||||
|
||||
boolean configFileExist = configFileExist();
|
||||
|
||||
// todo: Android 11
|
||||
for (PackageInfo pkg : pm.getInstalledPackages(PackageManager.GET_META_DATA)) {
|
||||
ApplicationInfo app = pkg.applicationInfo;
|
||||
if (!app.enabled) {
|
||||
continue;
|
||||
}
|
||||
if (app.metaData != null && app.metaData.containsKey("xposedminversion")) {
|
||||
String apkPath = pkg.applicationInfo.publicSourceDir;
|
||||
String apkName = context.getPackageManager().getApplicationLabel(pkg.applicationInfo).toString();
|
||||
if (TextUtils.isEmpty(apkPath)) {
|
||||
apkPath = pkg.applicationInfo.sourceDir;
|
||||
}
|
||||
if (!TextUtils.isEmpty(apkPath) && (!configFileExist || packageNameList == null || packageNameList.contains(app.packageName))) {
|
||||
XLog.d(TAG, "query installed module path " + apkPath);
|
||||
modulePathList.add(apkPath);
|
||||
}
|
||||
installedModuleList.add(Pair.create(pkg.applicationInfo.packageName, apkName));
|
||||
}
|
||||
}
|
||||
|
||||
final List<Pair<String, String>> installedModuleListFinal = installedModuleList;
|
||||
|
||||
new Thread(() -> {
|
||||
List<String> savedPackageNameList = loadPackageNameListFromFile(false);
|
||||
if (savedPackageNameList == null) {
|
||||
savedPackageNameList = new ArrayList<>();
|
||||
}
|
||||
List<Pair<String, String>> addPackageList = new ArrayList<>();
|
||||
for (Pair<String, String> packgagePair : installedModuleListFinal) {
|
||||
if (!savedPackageNameList.contains(packgagePair.first)) {
|
||||
XLog.d(TAG, "append " + packgagePair + " to " + XPOSED_MODULE_FILE_PATH);
|
||||
addPackageList.add(packgagePair);
|
||||
}
|
||||
}
|
||||
try {
|
||||
appendPackageNameToFile(addPackageList);
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
return modulePathList;
|
||||
}
|
||||
|
||||
// 从 sd 卡中加载指定文件,以加载指定的 xposed module
|
||||
private static List<String> loadPackageNameListFromFile(boolean loadActivedPackages) {
|
||||
File moduleFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH);
|
||||
if (!moduleFile.exists()) {
|
||||
return null;
|
||||
}
|
||||
List<String> modulePackageList = new ArrayList<>();
|
||||
|
||||
try (FileInputStream fileInputStream = new FileInputStream(moduleFile);
|
||||
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream))) {
|
||||
String modulePackageName;
|
||||
while ((modulePackageName = bufferedReader.readLine()) != null) {
|
||||
modulePackageName = modulePackageName.trim();
|
||||
if (modulePackageName.isEmpty() || (modulePackageName.startsWith("#") && loadActivedPackages)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (modulePackageName.startsWith("#")) {
|
||||
modulePackageName = modulePackageName.substring(1);
|
||||
}
|
||||
int index = modulePackageName.indexOf("#");
|
||||
if (index > 0) {
|
||||
modulePackageName = modulePackageName.substring(0, index);
|
||||
}
|
||||
XLog.d(TAG, "load " + XPOSED_MODULE_FILE_PATH + " file result, modulePackageName " + modulePackageName);
|
||||
modulePackageList.add(modulePackageName);
|
||||
}
|
||||
}
|
||||
catch (FileNotFoundException ignore) {
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
return modulePackageList;
|
||||
}
|
||||
|
||||
private static void appendPackageNameToFile(List<Pair<String, String>> packageNameList) throws IOException {
|
||||
|
||||
if (isEmpty(packageNameList)) {
|
||||
return;
|
||||
}
|
||||
|
||||
File moduleFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH);
|
||||
if (!moduleFile.exists()) {
|
||||
if (!moduleFile.createNewFile()) {
|
||||
throw new IllegalStateException("create " + XPOSED_MODULE_FILE_PATH + " err");
|
||||
}
|
||||
}
|
||||
try (FileOutputStream outputStream = new FileOutputStream(moduleFile, true);
|
||||
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) {
|
||||
for (Pair<String, String> packageInfo : packageNameList) {
|
||||
String packageName = packageInfo.first;
|
||||
String appName = packageInfo.second;
|
||||
writer.write(packageName + "#" + appName);
|
||||
writer.write("\n");
|
||||
XLog.d(TAG, "append new pkg to " + XPOSED_MODULE_FILE_PATH);
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
catch (FileNotFoundException ignore) {
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean configFileExist() {
|
||||
File moduleConfigFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH);
|
||||
return moduleConfigFile.exists();
|
||||
}
|
||||
|
||||
private static void closeStream(Closeable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isEmpty(Collection<?> collection) {
|
||||
if (collection == null || collection.size() == 0) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Context getAppContext() {
|
||||
return appContext;
|
||||
public static void initModules(Context context) {
|
||||
XposedInit.loadedPackagesInProcess.add(context.getPackageName());
|
||||
XResources.setPackageNameForResDir(context.getPackageName(), context.getPackageResourcePath());
|
||||
XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(
|
||||
XposedBridge.sLoadedPackageCallbacks);
|
||||
lpparam.packageName = context.getPackageName();
|
||||
lpparam.processName = ActivityThread.currentProcessName();
|
||||
lpparam.classLoader = context.getClassLoader();
|
||||
lpparam.appInfo = context.getApplicationInfo();
|
||||
lpparam.isFirstApplication = true;
|
||||
XC_LoadPackage.callAll(lpparam);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
package org.lsposed.lspatch.loader.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Process;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
|
|
@ -12,19 +9,6 @@ import java.nio.charset.StandardCharsets;
|
|||
|
||||
public class FileUtils {
|
||||
|
||||
//读写权限
|
||||
private static String[] PERMISSIONS_STORAGE = {
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
};
|
||||
|
||||
public static boolean isSdcardPermissionGranted(Context context) {
|
||||
int pid = android.os.Process.myPid();
|
||||
int uid = Process.myUid();
|
||||
return context.checkPermission(PERMISSIONS_STORAGE[0], pid, uid) == PackageManager.PERMISSION_GRANTED && context.checkPermission(PERMISSIONS_STORAGE[1], pid,
|
||||
uid) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
public static String readTextFromAssets(Context context, String assetsFileName) {
|
||||
if (context == null) {
|
||||
throw new IllegalStateException("context null");
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
package org.lsposed.lspatch.loader.util;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class XpatchUtils {
|
||||
final static String TAG = "LSP" + XpatchUtils.class.getSimpleName();
|
||||
|
||||
public static Context createAppContext() {
|
||||
try {
|
||||
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
|
||||
Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
|
||||
currentActivityThreadMethod.setAccessible(true);
|
||||
|
||||
Object activityThreadObj = currentActivityThreadMethod.invoke(null);
|
||||
|
||||
Field boundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication");
|
||||
boundApplicationField.setAccessible(true);
|
||||
Object mBoundApplication = boundApplicationField.get(activityThreadObj); // AppBindData
|
||||
if (mBoundApplication == null) {
|
||||
Log.e(TAG, "mBoundApplication null");
|
||||
return null;
|
||||
}
|
||||
Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info
|
||||
infoField.setAccessible(true);
|
||||
Object loadedApkObj = infoField.get(mBoundApplication); // LoadedApk
|
||||
if (loadedApkObj == null) {
|
||||
Log.e(TAG, "loadedApkObj null");
|
||||
return null;
|
||||
}
|
||||
Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
|
||||
Method createAppContextMethod = contextImplClass.getDeclaredMethod("createAppContext", activityThreadClass, loadedApkObj.getClass());
|
||||
createAppContextMethod.setAccessible(true);
|
||||
|
||||
Object context = createAppContextMethod.invoke(null, (ActivityThread) activityThreadObj, loadedApkObj);
|
||||
|
||||
if (context instanceof Context) {
|
||||
return (Context) context;
|
||||
}
|
||||
}
|
||||
catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isApkDebugable(Context context) {
|
||||
try {
|
||||
ApplicationInfo info = context.getApplicationInfo();
|
||||
return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
2
core
2
core
|
|
@ -1 +1 @@
|
|||
Subproject commit 9e47e9027c4244317085cce2a95ddcbb7b579ee0
|
||||
Subproject commit 9891fc4760e3ef806e909ca7aed83c7248991bf7
|
||||
|
|
@ -26,11 +26,12 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.jar.JarFile;
|
||||
|
||||
public class LSPatch {
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ public class LSPatch {
|
|||
}
|
||||
|
||||
@Parameter(description = "apks")
|
||||
private ArrayList<String> apkPaths = new ArrayList<>();
|
||||
private List<String> apkPaths = new ArrayList<>();
|
||||
|
||||
@Parameter(names = {"-h", "--help"}, help = true, order = 0, description = "Print this message")
|
||||
private boolean help = false;
|
||||
|
|
@ -72,6 +73,9 @@ public class LSPatch {
|
|||
@Parameter(names = {"-v", "--verbose"}, description = "Verbose output")
|
||||
private boolean verbose = false;
|
||||
|
||||
@Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk")
|
||||
private List<String> modules = new ArrayList<>();
|
||||
|
||||
private int dexFileCount = 0;
|
||||
|
||||
private static final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini";
|
||||
|
|
@ -195,7 +199,7 @@ public class LSPatch {
|
|||
try (var is = new ByteArrayInputStream(applicationName.getBytes(StandardCharsets.UTF_8))) {
|
||||
zFile.add(APPLICATION_NAME_ASSET_PATH, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when saving signature: " + e);
|
||||
throw new PatchError("Error when saving application name: " + e);
|
||||
}
|
||||
|
||||
// copy so and dex files into the unzipped apk
|
||||
|
|
@ -264,9 +268,11 @@ public class LSPatch {
|
|||
try (var is = new ByteArrayInputStream("42".getBytes(StandardCharsets.UTF_8))) {
|
||||
zFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when saving signature: " + e);
|
||||
throw new PatchError("Error when saving signature bypass level: " + e);
|
||||
}
|
||||
|
||||
embedModules(zFile);
|
||||
|
||||
System.out.println("Signing apk...");
|
||||
var sign = zFile.get("META-INF/MANIFEST.MF");
|
||||
if (sign != null)
|
||||
|
|
@ -286,6 +292,46 @@ public class LSPatch {
|
|||
}
|
||||
}
|
||||
|
||||
private void embedModules(ZFile zFile) {
|
||||
System.out.println("Embedding modules...");
|
||||
for (var module : modules) {
|
||||
var file = new File(module);
|
||||
if (!file.exists()) {
|
||||
System.err.println(file.getAbsolutePath() + " does not exist.");
|
||||
}
|
||||
|
||||
System.out.print("Embedding module ");
|
||||
|
||||
ManifestParser.Pair pair = null;
|
||||
try (JarFile jar = new JarFile(file)) {
|
||||
var manifest = jar.getEntry(ANDROID_MANIFEST_XML);
|
||||
if (manifest == null) {
|
||||
System.out.println();
|
||||
System.err.println(file.getAbsolutePath() + " is not a valid apk file.");
|
||||
continue;
|
||||
}
|
||||
pair = ManifestParser.parseManifestFile(jar.getInputStream(manifest));
|
||||
if (pair == null) {
|
||||
System.out.println();
|
||||
System.err.println(file.getAbsolutePath() + " is not a valid apk file.");
|
||||
continue;
|
||||
}
|
||||
System.out.println(pair.packageName);
|
||||
} catch (Throwable e) {
|
||||
System.out.println();
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
if (pair != null) {
|
||||
try (var is = new FileInputStream(file)) {
|
||||
zFile.add("assets/modules/" + pair.packageName, is);
|
||||
} catch (Throwable e) {
|
||||
System.err.println("Embed " + pair.packageName + " with error: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private byte[] modifyManifestFile(InputStream is) throws IOException {
|
||||
ModificationProperty property = new ModificationProperty();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue