├── core ├── .gitignore ├── libs │ ├── hooklib.aar │ ├── annotation.jar │ ├── xposedcompat.aar │ └── dalvik-dx-9.0.0_r3.jar ├── src │ └── main │ │ ├── res │ │ └── values │ │ │ └── strings.xml │ │ ├── java │ │ ├── android │ │ │ ├── content │ │ │ │ └── res │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── CompatibilityInfo.java │ │ │ │ │ └── XResForwarder.java │ │ │ └── app │ │ │ │ ├── package-info.java │ │ │ │ ├── LoadedApk.java │ │ │ │ ├── ActivityThread.java │ │ │ │ └── AndroidAppHelper.java │ │ ├── com │ │ │ └── wind │ │ │ │ └── xposed │ │ │ │ └── entry │ │ │ │ ├── XposedHookLoadPackageInner.java │ │ │ │ ├── util │ │ │ │ ├── XLog.java │ │ │ │ ├── SharedPrefUtils.java │ │ │ │ ├── PackageNameCache.java │ │ │ │ ├── FileUtils.java │ │ │ │ ├── PluginNativeLibExtractor.java │ │ │ │ ├── VMRuntime.java │ │ │ │ ├── NativeLibraryHelperCompat.java │ │ │ │ ├── XpatchUtils.java │ │ │ │ └── ReflectUtils.java │ │ │ │ ├── SandHookInitialization.java │ │ │ │ ├── XposedModuleLoader.java │ │ │ │ ├── hooker │ │ │ │ └── PackageSignatureHooker.java │ │ │ │ └── XposedModuleEntry.java │ │ └── de │ │ │ └── robv │ │ │ └── android │ │ │ └── xposed │ │ │ └── XposedHelper.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── values │ │ │ └── strings.xml │ │ └── layout │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── storm │ │ └── wind │ │ └── xposed │ │ ├── MainTestActivity.java │ │ └── XposedTestApplication.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── gradle.properties ├── gradlew.bat ├── README.md ├── publish.gradle ├── gradlew └── LICENSE /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /target 3 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name='xposed_module_loader' 3 | include ':core' 4 | 5 | -------------------------------------------------------------------------------- /core/libs/hooklib.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WindySha/xposed_module_loader/HEAD/core/libs/hooklib.aar -------------------------------------------------------------------------------- /core/libs/annotation.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WindySha/xposed_module_loader/HEAD/core/libs/annotation.jar -------------------------------------------------------------------------------- /core/libs/xposedcompat.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WindySha/xposed_module_loader/HEAD/core/libs/xposedcompat.aar -------------------------------------------------------------------------------- /core/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | library 3 | 4 | -------------------------------------------------------------------------------- /core/libs/dalvik-dx-9.0.0_r3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WindySha/xposed_module_loader/HEAD/core/libs/dalvik-dx-9.0.0_r3.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WindySha/xposed_module_loader/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WindySha/xposed_module_loader/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WindySha/xposed_module_loader/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /core/src/main/java/android/content/res/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains classes that are required for replacing resources. 3 | */ 4 | package android.content.res; 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Xposed Module Loader 4 | -------------------------------------------------------------------------------- /core/src/main/java/android/app/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains {@link android.app.AndroidAppHelper} with various methods for information about the current app. 3 | */ 4 | package android.app; 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jan 02 22:56:34 CST 2021 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | 16 | /.idea 17 | maven_signing.properties 18 | -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | #-keep class com.wind.xposed.entry.XposedModuleEntry { 2 | # public (); 3 | # public void init(); 4 | #} 5 | #-keep class de.robv.android.xposed.**{*;} 6 | #-keep class com.swift.sandhook.**{*;} 7 | #-keep class com.swift.sandhook.xposedcompat.**{*;} 8 | # 9 | #-dontwarn de.robv.android.xposed.XposedHelper 10 | -keep class com.wind.xposed.entry.XposedModuleEntry { 11 | public (); 12 | public void init(); 13 | } 14 | -keep class de.robv.android.xposed.**{*;} 15 | 16 | -dontwarn de.robv.android.xposed.XposedHelper -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | -------------------------------------------------------------------------------- /core/src/main/java/android/content/res/CompatibilityInfo.java: -------------------------------------------------------------------------------- 1 | package android.content.res; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class CompatibilityInfo implements Parcelable { 7 | @Override 8 | public int describeContents() { 9 | throw new UnsupportedOperationException("STUB"); 10 | } 11 | 12 | @Override 13 | public void writeToParcel(Parcel dest, int flags) { 14 | throw new UnsupportedOperationException("STUB"); 15 | } 16 | 17 | public static final Parcelable.Creator CREATOR = null; 18 | } -------------------------------------------------------------------------------- /core/src/main/java/android/app/LoadedApk.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.content.pm.ApplicationInfo; 4 | 5 | public final class LoadedApk { 6 | public ApplicationInfo getApplicationInfo() { 7 | throw new UnsupportedOperationException("STUB"); 8 | } 9 | 10 | public ClassLoader getClassLoader() { 11 | throw new UnsupportedOperationException("STUB"); 12 | } 13 | 14 | public String getPackageName() { 15 | throw new UnsupportedOperationException("STUB"); 16 | } 17 | 18 | public String getResDir() { 19 | throw new UnsupportedOperationException("STUB"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/src/main/java/android/app/ActivityThread.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.content.pm.ApplicationInfo; 4 | import android.content.res.CompatibilityInfo; 5 | 6 | public final class ActivityThread { 7 | public static ActivityThread currentActivityThread() { 8 | throw new UnsupportedOperationException("STUB"); 9 | } 10 | 11 | public static Application currentApplication() { 12 | throw new UnsupportedOperationException("STUB"); 13 | } 14 | 15 | public static String currentPackageName() { 16 | throw new UnsupportedOperationException("STUB"); 17 | } 18 | 19 | public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { 20 | throw new UnsupportedOperationException("STUB"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/XposedHookLoadPackageInner.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry; 2 | 3 | import com.wind.xposed.entry.hooker.PackageSignatureHooker; 4 | 5 | import de.robv.android.xposed.IXposedHookLoadPackage; 6 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 7 | 8 | /** 9 | * Created by Wind 10 | */ 11 | public class XposedHookLoadPackageInner implements IXposedHookLoadPackage { 12 | 13 | private static final String TAG = "XH_LoadPackageInner"; 14 | 15 | protected static XposedHookLoadPackageInner newIntance() { 16 | return new XposedHookLoadPackageInner(); 17 | } 18 | 19 | @Override 20 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { 21 | new PackageSignatureHooker().handleLoadPackage(lpparam); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class com.wind.xposed.entry.XposedModuleEntry { 2 | public (); 3 | public void init(); 4 | } 5 | 6 | -keep class com.wind.xpatch.proxy.**{*;} 7 | 8 | -keep class de.robv.android.xposed.**{*;} 9 | 10 | -keep class android.app.**{*;} 11 | -keep class android.content.**{*;} 12 | -keep class android.os.**{*;} 13 | 14 | -keep class android.view.**{*;} 15 | -keep class com.lody.whale.**{*;} 16 | -keep class com.android.internal.**{*;} 17 | -keep class xposed.dummy.**{*;} 18 | -keep class com.wind.xposed.entry.util.**{*;} 19 | 20 | -keep class com.swift.sandhook.**{*;} 21 | -keep class com.swift.sandhook.xposedcompat.**{*;} 22 | 23 | -dontwarn android.content.res.Resources 24 | -dontwarn android.content.res.Resources$Theme 25 | -dontwarn android.content.res.AssetManager 26 | -dontwarn android.content.res.TypedArray 27 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | version_name=3.0 16 | version_code=3 17 | android.disableResourceValidation=true 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /core/src/main/java/android/content/res/XResForwarder.java: -------------------------------------------------------------------------------- 1 | package android.content.res; 2 | 3 | /** 4 | * Instances of this class can be used for {@link XResources#setReplacement(String, String, String, Object)} 5 | * and its variants. They forward the resource request to a different {@link Resources} 6 | * instance with a possibly different ID. 7 | * 8 | *

Usually, instances aren't created directly but via {@link XModuleResources#fwd}. 9 | */ 10 | public class XResForwarder { 11 | private final Resources res; 12 | private final int id; 13 | 14 | /** 15 | * Creates a new instance. 16 | * 17 | * @param res The target {@link Resources} instance to forward requests to. 18 | * @param id The target resource ID. 19 | */ 20 | public XResForwarder(Resources res, int id) { 21 | this.res = res; 22 | this.id = id; 23 | } 24 | 25 | /** Returns the target {@link Resources} instance. */ 26 | public Resources getResources() { 27 | return res; 28 | } 29 | 30 | /** Returns the target resource ID. */ 31 | public int getId() { 32 | return id; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/util/XLog.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.util; 2 | 3 | import com.wind.xposed.entry.BuildConfig; 4 | 5 | public class XLog { 6 | 7 | private static boolean enableLog = true; 8 | 9 | public static void d(String tag, String msg) { 10 | if (enableLog) { 11 | android.util.Log.d(tag, msg); 12 | } 13 | } 14 | 15 | public static void v(String tag, String msg) { 16 | if (enableLog) { 17 | android.util.Log.v(tag, msg); 18 | } 19 | } 20 | 21 | public static void w(String tag, String msg) { 22 | if (enableLog) { 23 | android.util.Log.w(tag, msg); 24 | } 25 | } 26 | 27 | public static void i(String tag, String msg) { 28 | if (enableLog) { 29 | android.util.Log.i(tag, msg); 30 | } 31 | } 32 | 33 | public static void e(String tag, String msg) { 34 | if (enableLog) { 35 | android.util.Log.e(tag, msg); 36 | } 37 | } 38 | 39 | public static void e(String tag, String msg, Throwable tr) { 40 | if (enableLog) { 41 | android.util.Log.e(tag, msg, tr); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/util/SharedPrefUtils.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.util; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | public class SharedPrefUtils { 7 | 8 | private static Context appContext; 9 | 10 | private static final String SHARED_PREFERENE_FILE_PATH = "xpatch_wl_shared_pref"; 11 | 12 | public static void init(Context context) { 13 | appContext = context; 14 | } 15 | 16 | public static long getLong() { 17 | if (appContext == null) { 18 | return 0L; 19 | } 20 | SharedPreferences sharedPreferences = appContext.getSharedPreferences(SHARED_PREFERENE_FILE_PATH, Context.MODE_PRIVATE); 21 | long result = sharedPreferences.getLong("time", 0L); 22 | return result; 23 | } 24 | 25 | public static void putLong(long data) { 26 | if (appContext == null) { 27 | return; 28 | } 29 | SharedPreferences sharedPreferences = appContext.getSharedPreferences(SHARED_PREFERENE_FILE_PATH, Context.MODE_PRIVATE); 30 | SharedPreferences.Editor editor = sharedPreferences.edit(); 31 | editor.putLong("time", data); 32 | editor.apply(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/SandHookInitialization.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import com.swift.sandhook.SandHook; 7 | import com.swift.sandhook.SandHookConfig; 8 | import com.swift.sandhook.xposedcompat.XposedCompat; 9 | import com.wind.xposed.entry.util.XpatchUtils; 10 | 11 | /** 12 | * @author Windysha 13 | */ 14 | public class SandHookInitialization { 15 | 16 | public static void init(Context context) { 17 | Log.d("SandHookInitialization", "start init"); 18 | if (context == null) { 19 | Log.e("SandHookInitialization", "try to init SandHook, but app context is null !!!!"); 20 | return; 21 | } 22 | 23 | sandHookCompat(context); 24 | 25 | SandHookConfig.DEBUG = XpatchUtils.isApkDebugable(context); 26 | XposedCompat.cacheDir = context.getCacheDir(); 27 | XposedCompat.context = context; 28 | XposedCompat.classLoader = context.getClassLoader(); 29 | XposedCompat.isFirstApplication = true; 30 | } 31 | 32 | private static void sandHookCompat(Context context) { 33 | SandHook.disableVMInline(); 34 | SandHook.tryDisableProfile(context.getPackageName()); 35 | SandHook.disableDex2oatInline(false); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/storm/wind/xposed/MainTestActivity.java: -------------------------------------------------------------------------------- 1 | package com.storm.wind.xposed; 2 | 3 | import android.Manifest; 4 | import android.app.Activity; 5 | import android.content.pm.PackageManager; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | //import android.support.v4.app.ActivityCompat; 9 | import android.view.View; 10 | 11 | public class MainTestActivity extends Activity { 12 | 13 | //读写权限 14 | private static String[] PERMISSIONS_STORAGE = { 15 | Manifest.permission.READ_EXTERNAL_STORAGE, 16 | Manifest.permission.WRITE_EXTERNAL_STORAGE}; 17 | private static final int REQUEST_PERMISSION_CODE = 1; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | 24 | // 需要文件读写权限才能读取sd卡里,用于管理xposed module的文件:xposed_config/modules.list 25 | // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { 26 | // if (ActivityCompat.checkSelfPermission(this, 27 | // Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 28 | // ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_PERMISSION_CODE); 29 | // } 30 | // } 31 | } 32 | 33 | public void onClick(View view) { 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | compileSdkVersion 28 7 | defaultConfig { 8 | applicationId "com.storm.wind.xposed" 9 | minSdkVersion 21 10 | targetSdkVersion 30 11 | versionCode version_code as Integer 12 | versionName version_name 13 | 14 | multiDexEnabled false 15 | 16 | ndk { 17 | // abiFilters 'armeabi-v7a', 'arm64-v8a' 18 | abiFilters 'armeabi-v7a' 19 | } 20 | } 21 | buildTypes { 22 | debug { 23 | debuggable true 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | release { 28 | debuggable false 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | 34 | // applicationVariants.all { variant -> 35 | // variant.outputs.all { 36 | // outputFileName = "${variant.getFlavorName()}-${variant.versionName}.apk" 37 | // } 38 | // } 39 | } 40 | 41 | dependencies { 42 | implementation fileTree(dir: 'libs', include: ['*.jar']) 43 | // implementation project(':core') 44 | implementation("io.github.windysha:xposed_module_loader:1.0.4") 45 | } 46 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.kezong.fat-aar' // https://github.com/kezong/fat-aar-android 3 | 4 | apply from: '../publish.gradle' 5 | 6 | android { 7 | compileSdkVersion 31 8 | buildToolsVersion "26.0.3" 9 | 10 | defaultConfig { 11 | minSdkVersion 19 12 | targetSdkVersion 31 13 | 14 | 15 | ndk { 16 | abiFilters "armeabi-v7a", "arm64-v8a" 17 | } 18 | } 19 | buildTypes { 20 | debug { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | release { 25 | minifyEnabled false 26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 27 | } 28 | } 29 | } 30 | 31 | fataar { 32 | /** 33 | * If transitive is true, local jar module and remote library's dependencies will be embed. 34 | * If transitive is false, just embed first level dependency 35 | * Local aar project does not support transitive, always embed first level 36 | * Default value is false 37 | * @since 1.3.0 38 | */ 39 | transitive = true 40 | } 41 | 42 | dependencies { 43 | implementation fileTree(include: ['*.jar'], dir: 'libs') 44 | embed(name: 'hooklib', ext: 'aar') 45 | embed(name: 'xposedcompat', ext: 'aar') 46 | // embed 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3' 47 | } -------------------------------------------------------------------------------- /core/src/main/java/de/robv/android/xposed/XposedHelper.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed; 2 | 3 | import android.util.Log; 4 | 5 | import java.lang.reflect.Member; 6 | 7 | public class XposedHelper { 8 | 9 | private static final String TAG = "XposedHelper"; 10 | 11 | public static void initSeLinux(String processName) { 12 | // SELinuxHelper.initOnce(); 13 | // SELinuxHelper.initForProcess(processName); 14 | } 15 | 16 | public static boolean isIXposedMod(Class moduleClass) { 17 | // Log.d(TAG, "module's classLoader : " + moduleClass.getClassLoader() + ", super: " + moduleClass.getSuperclass()); 18 | // Log.d(TAG, "IXposedMod's classLoader : " + IXposedMod.class.getClassLoader()); 19 | return IXposedMod.class.isAssignableFrom(moduleClass); 20 | } 21 | 22 | 23 | public static XC_MethodHook.Unhook newUnHook(XC_MethodHook methodHook, Member member) { 24 | return methodHook.new Unhook(member); 25 | } 26 | 27 | public static void callInitZygote(String modulePath, Object moduleInstance) throws Throwable { 28 | IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam(); 29 | param.modulePath = modulePath; 30 | param.startsSystemServer = false; 31 | ((IXposedHookZygoteInit) moduleInstance).initZygote(param); 32 | } 33 | 34 | public static void beforeHookedMethod(XC_MethodHook methodHook, XC_MethodHook.MethodHookParam param) throws Throwable{ 35 | methodHook.beforeHookedMethod(param); 36 | } 37 | 38 | public static void afterHookedMethod(XC_MethodHook methodHook, XC_MethodHook.MethodHookParam param) throws Throwable{ 39 | methodHook.afterHookedMethod(param); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/util/PackageNameCache.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.util; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by Wind 12 | */ 13 | public class PackageNameCache { 14 | 15 | private static final String TAG = PackageNameCache.class.getSimpleName(); 16 | 17 | private Context mContext; 18 | private Map mPackageNameMap = new HashMap<>(); 19 | 20 | private static PackageNameCache instance; 21 | 22 | private PackageNameCache(Context context) { 23 | this.mContext = context; 24 | } 25 | 26 | public static PackageNameCache getInstance(Context context) { 27 | if (instance == null) { 28 | synchronized (PackageNameCache.class) { 29 | if (instance == null) { 30 | instance = new PackageNameCache(context); 31 | } 32 | } 33 | } 34 | return instance; 35 | } 36 | 37 | public String getPackageNameByPath(String apkPath) { 38 | if (apkPath == null || apkPath.length() == 0) { 39 | return ""; 40 | } 41 | String packageName = mPackageNameMap.get(apkPath); 42 | if (packageName != null && packageName.length() > 0) { 43 | return packageName; 44 | } 45 | packageName = ""; 46 | PackageManager pm = mContext.getPackageManager(); 47 | long startTime = System.currentTimeMillis(); 48 | PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES); 49 | XLog.d(TAG, "Get package name time -> " + (System.currentTimeMillis() - startTime) 50 | + " apkPath -> " + apkPath); 51 | if (info != null) { 52 | packageName = info.packageName; 53 | mPackageNameMap.put(apkPath, packageName); 54 | } 55 | return packageName; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/storm/wind/xposed/XposedTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.storm.wind.xposed; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import android.os.Bundle; 7 | import android.util.Log; 8 | 9 | import com.wind.xposed.entry.XposedModuleEntry; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import de.robv.android.xposed.XC_MethodHook; 15 | import de.robv.android.xposed.XposedHelpers; 16 | 17 | public class XposedTestApplication extends Application { 18 | 19 | static { 20 | List list = new ArrayList() { 21 | { 22 | add("/mnt/sdcard/app-debug.apk"); 23 | add("/data/data/com.storm.wind.xposed/files/app-debug.apk"); 24 | } 25 | }; 26 | XposedModuleEntry.init(null, list); 27 | // XposedModuleEntry.init(null, "/data/data/com.storm.wind.xposed/"); 28 | } 29 | 30 | @Override 31 | protected void attachBaseContext(Context base) { 32 | // List list = new ArrayList() { 33 | // { 34 | // add("/mnt/sdcard/app-debug.apk"); 35 | // add("/data/data/com.storm.wind.xposed/files/app-debug.apk"); 36 | // } 37 | // }; 38 | //// XposedModuleEntry.init(base, list); 39 | // XposedModuleEntry.init(base, null, false); 40 | 41 | super.attachBaseContext(base); 42 | } 43 | 44 | @Override 45 | public void onCreate() { 46 | super.onCreate(); 47 | hookOnCreate(); 48 | } 49 | 50 | private void hookOnCreate() { 51 | XposedHelpers.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new XC_MethodHook() { 52 | @Override 53 | protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 54 | super.beforeHookedMethod(param); 55 | Log.e("xiawanli", " beforeHookedMethod onCreate"); 56 | } 57 | 58 | @Override 59 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 60 | super.afterHookedMethod(param); 61 | Log.e("xiawanli", " beforeHookedMethod onCreate"); 62 | } 63 | }); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.util; 2 | 3 | import android.Manifest; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.os.Process; 7 | 8 | import java.io.BufferedReader; 9 | import java.io.Closeable; 10 | import java.io.InputStream; 11 | import java.io.InputStreamReader; 12 | 13 | public class FileUtils { 14 | 15 | //读写权限 16 | private static String[] PERMISSIONS_STORAGE = { 17 | Manifest.permission.READ_EXTERNAL_STORAGE, 18 | Manifest.permission.WRITE_EXTERNAL_STORAGE}; 19 | 20 | public static boolean isFilePermissionGranted(Context context) { 21 | int pid = android.os.Process.myPid(); 22 | int uid = Process.myUid(); 23 | return context.checkPermission(PERMISSIONS_STORAGE[0], pid, uid) == PackageManager.PERMISSION_GRANTED && 24 | context.checkPermission(PERMISSIONS_STORAGE[1], pid, uid) == PackageManager.PERMISSION_GRANTED; 25 | } 26 | 27 | public static String readTextFromAssets(Context context, String assetsFileName) { 28 | if (context == null) { 29 | return null; 30 | } 31 | try { 32 | InputStream is = context.getAssets().open(assetsFileName); 33 | return readTextFromInputStream(is); 34 | } catch (Exception e) { 35 | e.printStackTrace(); 36 | } 37 | return null; 38 | } 39 | 40 | public static String readTextFromInputStream(InputStream is) { 41 | InputStreamReader reader = null; 42 | BufferedReader bufferedReader = null; 43 | try { 44 | reader = new InputStreamReader(is, "UTF-8"); 45 | bufferedReader = new BufferedReader(reader); 46 | StringBuilder builder = new StringBuilder(); 47 | String str; 48 | while ((str = bufferedReader.readLine()) != null) { 49 | builder.append(str); 50 | } 51 | return builder.toString(); 52 | } catch (Exception e) { 53 | e.printStackTrace(); 54 | } finally { 55 | closeSafely(reader); 56 | closeSafely(bufferedReader); 57 | } 58 | return null; 59 | } 60 | 61 | private static void closeSafely(Closeable closeable) { 62 | try { 63 | if (closeable != null) { 64 | closeable.close(); 65 | } 66 | } catch (Exception e) { 67 | e.printStackTrace(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/util/PluginNativeLibExtractor.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.util; 2 | 3 | import static android.content.Context.MODE_PRIVATE; 4 | 5 | import android.content.Context; 6 | import android.content.SharedPreferences; 7 | import android.util.Log; 8 | 9 | import java.io.File; 10 | import java.util.Set; 11 | 12 | public class PluginNativeLibExtractor { 13 | 14 | private static final String TAG = "NativeLibExtractor"; 15 | private static final String SHARE_PREF_FILE_NAME = "xpatch_module_native_lib_config"; 16 | 17 | private static SharedPreferences sharedPreferences; 18 | 19 | public static void copySoFileIfNeeded(Context context, String libPath, String pluginApkPath) { 20 | boolean isMainProcess = XpatchUtils.isMainProcess(context); 21 | if (!isMainProcess) { 22 | return; 23 | } 24 | 25 | Set abiSet = NativeLibraryHelperCompat.getSupportAbiList(pluginApkPath); 26 | if (abiSet.isEmpty()) { 27 | Log.i(TAG, " plugin: " + pluginApkPath + " do not contains any so files."); 28 | return; 29 | } 30 | 31 | XpatchUtils.ensurePathExist(libPath); 32 | 33 | Log.i(TAG, " copySoFileIfNeeded procecess = " + XpatchUtils.getCurProcessName(context) + " isMainProcess = " + XpatchUtils.isMainProcess(context)); 34 | Log.i(TAG, " copyPluginSoFile libPath = " + libPath + " pluginApkPath = " + pluginApkPath); 35 | if (sharedPreferences == null) { 36 | sharedPreferences = context.getSharedPreferences(SHARE_PREF_FILE_NAME, MODE_PRIVATE); 37 | } 38 | String savedMd5 = getSavedApkFileMd5(sharedPreferences, pluginApkPath); 39 | String curMd5 = XpatchUtils.getFileMD5(new File(pluginApkPath)); 40 | Log.i(TAG, " copyPluginSoFile savedMd5 = " + savedMd5 + " curMd5 = " + curMd5); 41 | if (savedMd5 == null || savedMd5.isEmpty() || !savedMd5.equals(curMd5)) { 42 | NativeLibraryHelperCompat.copyNativeBinaries(new File(pluginApkPath), new File(libPath)); 43 | saveApkFileMd5(sharedPreferences, pluginApkPath, curMd5); 44 | } else { 45 | Log.d(TAG, "plugin is not changed, no need to copy so file again!"); 46 | } 47 | } 48 | 49 | private static String getSavedApkFileMd5(SharedPreferences sp, String key) { 50 | return sp.getString(key, ""); 51 | } 52 | 53 | private static void saveApkFileMd5(SharedPreferences sp, String key, String md5) { 54 | sp.edit().putString(key, md5).apply(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/badge/license-Apache2.0-brightgreen.svg?style=flat) 2 | ![](https://img.shields.io/badge/release-1.0.4-red.svg?style=flat) 3 | ![](https://img.shields.io/badge/Android-5%20--%2012-blue.svg?style=flat) 4 | ![](https://img.shields.io/badge/arch-armeabi--v7a%20%7C%20arm64--v8a-blue.svg?style=flat) 5 | 6 | # Introduction 7 | This is a library used to load xposed module files. 8 | 9 | # Features 10 | 11 | * Support loading xposed modules by the apk file path; 12 | * Support loading all the xposed modules installed in the device; 13 | * Support loading all the native libraries in the xposed modules; 14 | * Support importing xposed styled java hooking framework to android projects; 15 | 16 | 17 | # Usage 18 | ## 1. Add dependency to build.gradle file 19 | 20 | This tool is published on [Maven Central](https://search.maven.org/). 21 | 22 | ```Gradle 23 | allprojects { 24 | repositories { 25 | mavenCentral() 26 | } 27 | } 28 | ``` 29 | 30 | ```Gradle 31 | android { 32 | defaultConfig { 33 | ndk { 34 | abiFilters 'armeabi-v7a', 'arm64-v8a' 35 | } 36 | } 37 | } 38 | 39 | dependencies { 40 | implementation 'io.github.windysha:xposed_module_loader:1.0.4' 41 | } 42 | ``` 43 | 44 | ## 2. Add init code to the Application file. 45 | * Load xposed modules by file paths: 46 | ``` 47 | @Override 48 | protected void attachBaseContext(Context base) { 49 | List list = new ArrayList() { 50 | { 51 | add("/mnt/sdcard/xposed_module.apk"); // app need to hava permission read files in the sdcard. 52 | add("/data/data/com.storm.wind.xposed/files/xposed_module.apk"); 53 | } 54 | }; 55 | XposedModuleEntry.init(base, list); 56 | super.attachBaseContext(base); 57 | } 58 | ``` 59 | * Load xposed modules by file directory: 60 | ``` 61 | @Override 62 | protected void attachBaseContext(Context base) { 63 | // all xposed module files in the dir /data/data/package_name/ will be loaded. 64 | XposedModuleEntry.init(base, "/data/data/package_name/"); 65 | super.attachBaseContext(base); 66 | } 67 | ``` 68 | * Load all xposed modules installed in the devices: 69 | ``` 70 | @Override 71 | protected void attachBaseContext(Context base) { 72 | XposedModuleEntry.init(base); 73 | super.attachBaseContext(base); 74 | } 75 | ``` 76 | * Only init java hook framework, do not load any xposed modules: 77 | ``` 78 | @Override 79 | protected void attachBaseContext(Context base) { 80 | XposedModuleEntry.init(base, null, false); 81 | super.attachBaseContext(base); 82 | } 83 | ``` 84 | 85 | # Applied 86 | Early version of [Xpatch](https://github.com/WindySha/Xpatch) use this library to load xposed modules。 87 | 88 | # Reference 89 | [SandHook](https://github.com/asLody/SandHook) 90 | 91 | # License 92 | ``` 93 | Copyright 2021 WindySha 94 | 95 | Licensed under the Apache License, Version 2.0 (the "License"); 96 | you may not use this file except in compliance with the License. 97 | You may obtain a copy of the License at 98 | 99 | http://www.apache.org/licenses/LICENSE-2.0 100 | 101 | Unless required by applicable law or agreed to in writing, software 102 | distributed under the License is distributed on an "AS IS" BASIS, 103 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 104 | See the License for the specific language governing permissions and 105 | limitations under the License. 106 | ``` 107 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/util/VMRuntime.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.util; 2 | 3 | import android.os.Build; 4 | 5 | import java.lang.reflect.Method; 6 | 7 | /** 8 | * Ref: https://zhuanlan.zhihu.com/p/364884283 9 | */ 10 | public class VMRuntime { 11 | public static void setHiddenApiExemptions(final String[] signaturePrefixes) { 12 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 13 | try { 14 | final Method method_getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", 15 | String.class, Class[].class); 16 | final Method method_forName = Class.class.getDeclaredMethod("forName", String.class); 17 | final Class class_VMRuntime = (Class) method_forName.invoke(null, "dalvik.system.VMRuntime"); 18 | final Method method_getRuntime = (Method) method_getDeclaredMethod.invoke(class_VMRuntime, 19 | "getRuntime", null); 20 | final Object object_VMRuntime = method_getRuntime.invoke(null); 21 | 22 | Method setHiddenApiExemptions = null; 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 24 | final Class class_Unsafe = Class.forName("sun.misc.Unsafe"); 25 | final Method method_getUnsafe = class_Unsafe.getDeclaredMethod("getUnsafe"); 26 | final Object object_Unsafe = method_getUnsafe.invoke(null); 27 | 28 | final Method method_getLong = class_Unsafe.getDeclaredMethod("getLong", long.class); 29 | final Method method_putLong = class_Unsafe.getDeclaredMethod("putLong", long.class, long.class); 30 | 31 | final Method method_getInt = class_Unsafe.getDeclaredMethod("getInt", long.class); 32 | // final Method method_putInt = class_Unsafe.getDeclaredMethod("putInt", long.class, int.class); 33 | 34 | final Method method_addressOf = class_VMRuntime.getDeclaredMethod("addressOf", Object.class); 35 | final Method method_newNonMovableArray = class_VMRuntime.getDeclaredMethod("newNonMovableArray", Class.class, int.class); 36 | 37 | final Method[] declaredMethods = class_VMRuntime.getDeclaredMethods(); 38 | final int length = declaredMethods.length; 39 | final Method[] array = (Method[]) method_newNonMovableArray.invoke(object_VMRuntime, 40 | Method.class, length); 41 | System.arraycopy(declaredMethods, 0, array, 0, length); 42 | 43 | // http://aosp.opersys.com/xref/android-11.0.0_r3/xref/art/runtime/mirror/executable.h 44 | // uint64_t Executable::art_method_ 45 | final int offset_art_method_ = 24; 46 | 47 | final long address = (long) method_addressOf.invoke(object_VMRuntime, (Object) array); 48 | long min = Long.MAX_VALUE, min_second = Long.MAX_VALUE, max = Long.MIN_VALUE; 49 | for (int k = 0; k < length; ++k) { 50 | final long address_Method = (int) method_getInt.invoke(object_Unsafe, address + k * Integer.BYTES); 51 | final long address_art_method = (long) method_getLong.invoke(object_Unsafe, 52 | address_Method + offset_art_method_); 53 | if (min >= address_art_method) { 54 | min = address_art_method; 55 | } else if (min_second >= address_art_method) { 56 | min_second = address_art_method; 57 | } 58 | if (max <= address_art_method) { 59 | max = address_art_method; 60 | } 61 | } 62 | 63 | final long size_art_method = min_second - min; 64 | if (size_art_method > 0 && size_art_method < 100) { 65 | for (min += size_art_method; min < max; min += size_art_method) { 66 | final long address_Method = (int) method_getInt.invoke(object_Unsafe, address); 67 | method_putLong.invoke(object_Unsafe, 68 | address_Method + offset_art_method_, min); 69 | final String name = array[0].getName(); 70 | if ("setHiddenApiExemptions".equals(name)) { 71 | setHiddenApiExemptions = array[0]; 72 | break; 73 | } 74 | } 75 | } 76 | } else { 77 | setHiddenApiExemptions = (Method) method_getDeclaredMethod.invoke(class_VMRuntime, 78 | "setHiddenApiExemptions", new Class[]{String[].class}); 79 | } 80 | 81 | if (setHiddenApiExemptions != null) { 82 | setHiddenApiExemptions.invoke(object_VMRuntime, (Object) signaturePrefixes); 83 | } 84 | } catch (final Exception ignored) { 85 | } 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /publish.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | 4 | ext { 5 | PUBLISH_GROUP_ID = 'io.github.windysha' 6 | PUBLISH_ARTIFACT_ID = 'xposed_module_loader' 7 | PUBLISH_ARTIFACT_NAME = 'Xposed Module Loader.' 8 | PUBLISH_VERSION_NAME = '1.0.4' 9 | 10 | POM_URL = 'https://github.com/WindySha/xposed_module_loader' 11 | POM_DESCRIPTION = 'This is a library that can load xposed module from any path or installed xposed module.' 12 | 13 | POM_DEVELOPER_ID = 'WindySha' 14 | POM_DEVELOPER_NAME = 'WindySha' 15 | POM_DEVELOPER_EMAIL = '819170366@qq.com' 16 | POM_DEVELOPER_URL = 'https://windysha.github.io/' 17 | 18 | POM_SCM_URL = 'https://github.com/WindySha/xposed_module_loader/tree/master' 19 | POM_SCM_CONNECTION = 'scm:git:https://github.com/WindySha/xposed_module_loader.git' 20 | } 21 | 22 | task androidSourcesJar(type: Jar) { 23 | archiveClassifier.set('sources') 24 | if (project.plugins.findPlugin("com.android.library")) { 25 | // For Android libraries 26 | from android.sourceSets.main.java.srcDirs 27 | from android.sourceSets.main.kotlin.srcDirs 28 | } else { 29 | // For pure Kotlin libraries, in case you have them 30 | from sourceSets.main.java.srcDirs 31 | from sourceSets.main.kotlin.srcDirs 32 | } 33 | } 34 | 35 | ext["signing.keyId"] = '' 36 | ext["signing.password"] = '' 37 | ext["signing.secretKeyRingFile"] = '' 38 | ext["ossrhUsername"] = '' 39 | ext["ossrhPassword"] = '' 40 | 41 | File secretPropsFile = project.rootProject.file('maven_signing.properties') 42 | if (secretPropsFile.exists()) { 43 | println "Found secret props file, loading props" 44 | Properties p = new Properties() 45 | p.load(new FileInputStream(secretPropsFile)) 46 | p.each { name, value -> 47 | ext[name] = value 48 | } 49 | } else { 50 | println "No props file, loading env vars" 51 | } 52 | publishing { 53 | publications { 54 | release(MavenPublication) { 55 | // The coordinates of the library, being set from variables that 56 | // we'll set up in a moment 57 | groupId PUBLISH_GROUP_ID 58 | artifactId PUBLISH_ARTIFACT_ID 59 | version PUBLISH_VERSION_NAME 60 | 61 | afterEvaluate { 62 | artifact tasks.getByName("bundleReleaseAar") 63 | artifact androidSourcesJar 64 | } 65 | 66 | // Two artifacts, the `aar` and the sources 67 | // artifact("$buildDir/outputs/aar/${project.getName()}-release.aar") 68 | // artifact androidSourcesJar 69 | 70 | // Self-explanatory metadata for the most part 71 | pom { 72 | name = PUBLISH_ARTIFACT_NAME 73 | description = POM_DESCRIPTION 74 | url = POM_URL 75 | licenses { 76 | license { 77 | //协议类型,一般默认Apache License2.0的话不用改: 78 | name = 'The Apache License, Version 2.0' 79 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 80 | } 81 | } 82 | developers { 83 | developer { 84 | id = POM_DEVELOPER_ID 85 | name = POM_DEVELOPER_NAME 86 | email = POM_DEVELOPER_EMAIL 87 | url = POM_DEVELOPER_URL 88 | } 89 | } 90 | // Version control info, if you're using GitHub, follow the format as seen here 91 | scm { 92 | url = POM_SCM_URL 93 | connection = POM_SCM_CONNECTION 94 | developerConnection = POM_SCM_CONNECTION 95 | } 96 | // A slightly hacky fix so that your POM will include any transitive dependencies 97 | // that your library builds upon 98 | // https://github.com/kezong/fat-aar-android/issues/45 99 | withXml { 100 | def dependenciesNode = asNode().appendNode('dependencies') 101 | 102 | //Iterate over the compile dependencies, adding a node for each 103 | project.configurations.implementation.allDependencies.each { 104 | def hasGroup = it.group != null 105 | def hasName = (it.name != null || "unspecified".equals(it.name)) 106 | def hasVersion = it.version != null 107 | 108 | if (hasGroup && hasName && hasVersion) { 109 | def dependencyNode = dependenciesNode.appendNode('dependency') 110 | dependencyNode.appendNode('groupId', it.group) 111 | dependencyNode.appendNode('artifactId', it.name) 112 | dependencyNode.appendNode('version', it.version) 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | repositories { 120 | maven { 121 | name = "mavenCentral" 122 | url = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" 123 | 124 | // The username and password we've fetched earlier 125 | credentials { 126 | username ossrhUsername 127 | password ossrhPassword 128 | } 129 | } 130 | } 131 | } 132 | signing { 133 | sign publishing.publications 134 | } 135 | // https://s01.oss.sonatype.org/#stagingRepositories 136 | // publish dst: https://repo.maven.apache.org/maven2/io/github/windysha/xposed_module_loader/ -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/util/NativeLibraryHelperCompat.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.util; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import android.os.Process; 6 | import android.util.Log; 7 | 8 | import java.io.File; 9 | import java.util.Collections; 10 | import java.util.Enumeration; 11 | import java.util.HashSet; 12 | import java.util.Set; 13 | import java.util.zip.ZipEntry; 14 | import java.util.zip.ZipFile; 15 | 16 | public class NativeLibraryHelperCompat { 17 | private static final String TAG = "NativeLibraryHelper"; 18 | 19 | public static int copyNativeBinaries(File apkFile, File sharedLibraryDir) { 20 | Log.i(TAG, " copyNativeBinaries !!! apkFile = " + apkFile.getAbsolutePath() + " sharedLibraryDir = " + sharedLibraryDir.getAbsolutePath()); 21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 22 | return copyNativeBinariesAfterL(apkFile, sharedLibraryDir); 23 | } else { 24 | return copyNativeBinariesBeforeL(apkFile, sharedLibraryDir); 25 | } 26 | } 27 | 28 | private static int copyNativeBinariesBeforeL(File apkFile, File sharedLibraryDir) { 29 | try { 30 | String className = "com.android.internal.content.NativeLibraryHelper"; 31 | Object result = ReflectUtils.callMethod(className, 32 | null, "copyNativeBinariesIfNeededLI", 33 | apkFile, sharedLibraryDir); 34 | if (result != null) { 35 | return (int) result; 36 | } 37 | } catch (Throwable e) { 38 | e.printStackTrace(); 39 | } 40 | return -1; 41 | } 42 | 43 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 44 | private static int copyNativeBinariesAfterL(File apkFile, File sharedLibraryDir) { 45 | try { 46 | String handleClassName = "com.android.internal.content.NativeLibraryHelper$Handle"; 47 | Object handle = ReflectUtils.callMethod(handleClassName, 48 | null, "create", apkFile); 49 | if (handle == null) { 50 | return -1; 51 | } 52 | 53 | String abi = null; 54 | Set abiSet = getSupportAbiList(apkFile.getAbsolutePath()); 55 | if (abiSet.isEmpty()) { 56 | return 0; 57 | } 58 | boolean is64Bit = is64bit(); 59 | String className = "com.android.internal.content.NativeLibraryHelper"; 60 | if (is64Bit && contain64bitAbi(abiSet)) { 61 | if (Build.SUPPORTED_64_BIT_ABIS.length > 0) { 62 | int abiIndex = (int) ReflectUtils.callMethod(className, 63 | null, 64 | "findSupportedAbi", 65 | handle, Build.SUPPORTED_64_BIT_ABIS); 66 | if (abiIndex >= 0) { 67 | abi = Build.SUPPORTED_64_BIT_ABIS[abiIndex]; 68 | } 69 | } 70 | } else { 71 | if (Build.SUPPORTED_32_BIT_ABIS.length > 0) { 72 | int abiIndex = (int) ReflectUtils.callMethod(className, 73 | null, 74 | "findSupportedAbi", 75 | handle, Build.SUPPORTED_32_BIT_ABIS); 76 | if (abiIndex >= 0) { 77 | abi = Build.SUPPORTED_32_BIT_ABIS[abiIndex]; 78 | } 79 | } 80 | } 81 | Log.i(TAG, " is64Bit=" + is64Bit + " abi = " + abi + " abiSet = " + abiSet + " sharedLibraryDir =" + sharedLibraryDir); 82 | if (abi == null) { 83 | Log.e(TAG, "Not match any abi." + apkFile.getAbsolutePath()); 84 | return -1; 85 | } 86 | int result = (int) ReflectUtils.callMethod(className, 87 | null, 88 | "copyNativeBinaries", 89 | handle, sharedLibraryDir, abi); 90 | Log.i(TAG, "copyNativeBinaries result = " + result + " apkFile path = " + apkFile.getAbsolutePath()); 91 | return result; 92 | } catch (Exception e) { 93 | e.printStackTrace(); 94 | } 95 | return -1; 96 | } 97 | 98 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 99 | public static boolean is64bitAbi(String abi) { 100 | return "arm64-v8a".equals(abi) 101 | || "x86_64".equals(abi) 102 | || "mips64".equals(abi); 103 | } 104 | 105 | public static boolean is32bitAbi(String abi) { 106 | return "armeabi".equals(abi) 107 | || "armeabi-v7a".equals(abi) 108 | || "mips".equals(abi) 109 | || "x86".equals(abi); 110 | } 111 | 112 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 113 | public static boolean contain64bitAbi(Set supportedABIs) { 114 | for (String supportedAbi : supportedABIs) { 115 | if (is64bitAbi(supportedAbi)) { 116 | return true; 117 | } 118 | } 119 | return false; 120 | } 121 | 122 | public static Set getSupportAbiList(String apk) { 123 | try { 124 | ZipFile apkFile = new ZipFile(apk); 125 | Enumeration entries = apkFile.entries(); 126 | Set supportedABIs = new HashSet(); 127 | while (entries.hasMoreElements()) { 128 | ZipEntry entry = entries.nextElement(); 129 | String name = entry.getName(); 130 | if (name.contains("../")) { 131 | continue; 132 | } 133 | if (name.startsWith("lib/") && !entry.isDirectory() && name.endsWith(".so")) { 134 | String supportedAbi = name.substring(name.indexOf("/") + 1, name.lastIndexOf("/")); 135 | supportedABIs.add(supportedAbi); 136 | } 137 | } 138 | return supportedABIs; 139 | } catch (Exception e) { 140 | e.printStackTrace(); 141 | } 142 | return Collections.emptySet(); 143 | } 144 | 145 | public static boolean is64bit() { 146 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 147 | return false; 148 | } 149 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 150 | return Process.is64Bit(); 151 | } 152 | Object runtime = ReflectUtils.callMethod("dalvik.system.VMRuntime", null, "getRuntime"); 153 | Object is64Bit = ReflectUtils.callMethod("dalvik.system.VMRuntime", runtime, "is64Bit"); 154 | if (is64Bit == null) return true; 155 | return (boolean) is64Bit; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/XposedModuleLoader.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.pm.ApplicationInfo; 5 | import android.util.Log; 6 | 7 | import com.wind.xposed.entry.util.XLog; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.InputStreamReader; 14 | import java.lang.reflect.Method; 15 | 16 | import dalvik.system.DexClassLoader; 17 | import de.robv.android.xposed.IXposedHookInitPackageResources; 18 | import de.robv.android.xposed.IXposedHookLoadPackage; 19 | import de.robv.android.xposed.IXposedHookZygoteInit; 20 | import de.robv.android.xposed.XposedBridge; 21 | import de.robv.android.xposed.XposedHelper; 22 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 23 | 24 | public class XposedModuleLoader { 25 | 26 | private static final String TAG = "XposedModuleLoader"; 27 | 28 | public static boolean loadModule(final String moduleApkPath, String moduleOdexDir, String moduleLibPath, 29 | final ApplicationInfo currentApplicationInfo, ClassLoader appClassLoader) { 30 | 31 | XLog.i(TAG, "Loading modules from " + moduleApkPath); 32 | 33 | if (!new File(moduleApkPath).exists()) { 34 | Log.e(TAG, moduleApkPath + " does not exist"); 35 | return false; 36 | } 37 | 38 | // use system classloader to load asset to avoid the app has file assets/xposed_init 39 | ClassLoader assetLoader = new DexClassLoader(moduleApkPath, moduleOdexDir, moduleLibPath, ClassLoader.getSystemClassLoader()); 40 | InputStream is = assetLoader.getResourceAsStream("assets/xposed_init"); 41 | if (is == null) { 42 | Log.i(TAG, "assets/xposed_init not found in the APK"); 43 | return false; 44 | } 45 | 46 | ClassLoader mcl = new DexClassLoader(moduleApkPath, moduleOdexDir, moduleLibPath, appClassLoader); 47 | BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is)); 48 | try { 49 | String moduleClassName; 50 | while ((moduleClassName = moduleClassesReader.readLine()) != null) { 51 | moduleClassName = moduleClassName.trim(); 52 | if (moduleClassName.isEmpty() || moduleClassName.startsWith("#")) 53 | continue; 54 | 55 | try { 56 | XLog.i(TAG, " Loading class " + moduleClassName); 57 | Class moduleClass = mcl.loadClass(moduleClassName); 58 | 59 | if (!XposedHelper.isIXposedMod(moduleClass)) { 60 | Log.i(TAG, " This class doesn't implement any sub-interface of IXposedMod, skipping it"); 61 | continue; 62 | } else if (IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) { 63 | Log.i(TAG, " This class requires resource-related hooks (which are disabled), skipping it."); 64 | continue; 65 | } 66 | 67 | final Object moduleInstance = moduleClass.newInstance(); 68 | if (moduleInstance instanceof IXposedHookZygoteInit) { 69 | XposedHelper.callInitZygote(moduleApkPath, moduleInstance); 70 | } 71 | 72 | if (moduleInstance instanceof IXposedHookLoadPackage) { 73 | // hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance)); 74 | IXposedHookLoadPackage.Wrapper wrapper = new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance); 75 | XposedBridge.CopyOnWriteSortedSet xc_loadPackageCopyOnWriteSortedSet = new XposedBridge.CopyOnWriteSortedSet<>(); 76 | xc_loadPackageCopyOnWriteSortedSet.add(wrapper); 77 | XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(xc_loadPackageCopyOnWriteSortedSet); 78 | lpparam.packageName = currentApplicationInfo.packageName; 79 | lpparam.processName = getCurrentProcessName(currentApplicationInfo);; 80 | lpparam.classLoader = appClassLoader; 81 | lpparam.appInfo = currentApplicationInfo; 82 | lpparam.isFirstApplication = true; 83 | XC_LoadPackage.callAll(lpparam); 84 | } 85 | 86 | if (moduleInstance instanceof IXposedHookInitPackageResources) { 87 | // hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance)); 88 | // TODO: Support Resource hook 89 | } 90 | 91 | } catch (Throwable t) { 92 | Log.e(TAG, " error ", t); 93 | } 94 | } 95 | } catch (IOException e) { 96 | Log.e(TAG, " error ", e); 97 | } finally { 98 | try { 99 | is.close(); 100 | } catch (IOException ignored) { 101 | } 102 | } 103 | return true; 104 | } 105 | 106 | public static void startInnerHook(ApplicationInfo applicationInfo, ClassLoader originClassLoader) { 107 | IXposedHookLoadPackage.Wrapper wrapper = new IXposedHookLoadPackage.Wrapper(XposedHookLoadPackageInner.newIntance()); 108 | 109 | XposedBridge.CopyOnWriteSortedSet xc_loadPackageCopyOnWriteSortedSet = new XposedBridge.CopyOnWriteSortedSet<>(); 110 | xc_loadPackageCopyOnWriteSortedSet.add(wrapper); 111 | 112 | XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(xc_loadPackageCopyOnWriteSortedSet); 113 | 114 | lpparam.packageName = applicationInfo.packageName; 115 | lpparam.processName = getCurrentProcessName(applicationInfo); 116 | lpparam.classLoader = originClassLoader; 117 | lpparam.appInfo = applicationInfo; 118 | lpparam.isFirstApplication = true; 119 | 120 | XC_LoadPackage.callAll(lpparam); 121 | } 122 | 123 | private static String currentProcessName = null; 124 | 125 | @SuppressLint("DiscouragedPrivateApi") 126 | private static String getCurrentProcessName(ApplicationInfo applicationInfo) { 127 | if (currentProcessName != null) return currentProcessName; 128 | 129 | currentProcessName = applicationInfo.packageName; 130 | try { 131 | Class activityThread_clazz = Class.forName("android.app.ActivityThread"); 132 | Method method = activityThread_clazz.getDeclaredMethod("currentProcessName"); 133 | method.setAccessible(true); 134 | currentProcessName = (String) method.invoke(null); 135 | } catch (Exception e) { 136 | e.printStackTrace(); 137 | } 138 | return currentProcessName; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/util/XpatchUtils.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.util; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | import android.content.pm.ApplicationInfo; 6 | import android.util.Log; 7 | 8 | import java.io.BufferedInputStream; 9 | import java.io.BufferedReader; 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.IOException; 13 | import java.io.InputStreamReader; 14 | import java.lang.reflect.Field; 15 | import java.lang.reflect.InvocationTargetException; 16 | import java.lang.reflect.Method; 17 | import java.math.BigInteger; 18 | import java.security.MessageDigest; 19 | import java.security.NoSuchAlgorithmException; 20 | 21 | public class XpatchUtils { 22 | 23 | private static String sCurProcessName = null; 24 | 25 | public static Context createAppContext() { 26 | 27 | // LoadedApk.makeApplication() 28 | // ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); 29 | 30 | try { 31 | Class activityThreadClass = Class.forName("android.app.ActivityThread"); 32 | Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); 33 | currentActivityThreadMethod.setAccessible(true); 34 | 35 | Object activityThreadObj = currentActivityThreadMethod.invoke(null); 36 | 37 | Field boundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication"); 38 | boundApplicationField.setAccessible(true); 39 | Object mBoundApplication = boundApplicationField.get(activityThreadObj); // AppBindData 40 | 41 | Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info 42 | infoField.setAccessible(true); 43 | Object loadedApkObj = infoField.get(mBoundApplication); // LoadedApk 44 | 45 | Class contextImplClass = Class.forName("android.app.ContextImpl"); 46 | Method createAppContextMethod = contextImplClass.getDeclaredMethod("createAppContext", activityThreadClass, loadedApkObj.getClass()); 47 | createAppContextMethod.setAccessible(true); 48 | 49 | Object context = createAppContextMethod.invoke(null, activityThreadObj, loadedApkObj); 50 | 51 | if (context instanceof Context) { 52 | return (Context) context; 53 | } 54 | 55 | } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { 56 | e.printStackTrace(); 57 | } 58 | return null; 59 | } 60 | 61 | public static boolean isApkDebugable(Context context) { 62 | try { 63 | ApplicationInfo info = context.getApplicationInfo(); 64 | return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; 65 | } catch (Exception e) { 66 | } 67 | return false; 68 | } 69 | 70 | public static String getCurProcessName(Context context) { 71 | String procName = sCurProcessName; 72 | if (procName != null && !procName.isEmpty()) { 73 | return procName; 74 | } 75 | try { 76 | int pid = android.os.Process.myPid(); 77 | ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 78 | for (ActivityManager.RunningAppProcessInfo appProcess : mActivityManager.getRunningAppProcesses()) { 79 | if (appProcess.pid == pid) { 80 | Log.d("Process", "processName = " + appProcess.processName); 81 | sCurProcessName = appProcess.processName; 82 | return sCurProcessName; 83 | } 84 | } 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | } 88 | sCurProcessName = getCurProcessNameFromProc(); 89 | return sCurProcessName; 90 | } 91 | 92 | private static String getCurProcessNameFromProc() { 93 | BufferedReader cmdlineReader = null; 94 | try { 95 | cmdlineReader = new BufferedReader(new InputStreamReader( 96 | new FileInputStream( 97 | "/proc/" + android.os.Process.myPid() + "/cmdline"), 98 | "iso-8859-1")); 99 | int c; 100 | StringBuilder processName = new StringBuilder(); 101 | while ((c = cmdlineReader.read()) > 0) { 102 | processName.append((char) c); 103 | } 104 | Log.d("Process", "get processName = " + processName.toString()); 105 | return processName.toString(); 106 | } catch (Throwable e) { 107 | // ignore 108 | } finally { 109 | if (cmdlineReader != null) { 110 | try { 111 | cmdlineReader.close(); 112 | } catch (Exception e) { 113 | // ignore 114 | } 115 | } 116 | } 117 | return null; 118 | } 119 | 120 | public static boolean isMainProcess(Context context) { 121 | String processName = getCurProcessName(context); 122 | if (processName != null && processName.contains(":")) { 123 | return false; 124 | } 125 | return (processName != null && processName.equals(context.getPackageName())); 126 | } 127 | 128 | public static String getFileMD5(File file) { 129 | if (!file.isFile()) { 130 | return ""; 131 | } 132 | MessageDigest digest = null; 133 | BufferedInputStream in = null; 134 | byte buffer[] = new byte[1024]; 135 | int len; 136 | try { 137 | digest = MessageDigest.getInstance("MD5"); 138 | in = new BufferedInputStream(new FileInputStream(file)); 139 | while ((len = in.read(buffer, 0, buffer.length)) != -1) { 140 | digest.update(buffer, 0, len); 141 | } 142 | String md5Result = byteArrayToHex(digest.digest()); 143 | return md5Result; 144 | } catch (Exception e) { 145 | return ""; 146 | } finally { 147 | if (in != null) { 148 | try { 149 | in.close(); 150 | } catch (IOException ignored) { 151 | } 152 | } 153 | } 154 | } 155 | 156 | public static String byteArrayToHex(byte[] byteArray) { 157 | if (byteArray == null || byteArray.length <= 0) { 158 | return ""; 159 | } 160 | // 首先初始化一个字符数组,用来存放每个16进制字符 161 | char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 162 | // new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方)) 163 | char[] resultCharArray = new char[byteArray.length * 2]; 164 | // 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去 165 | int index = 0; 166 | for (byte b : byteArray) { 167 | resultCharArray[index++] = hexDigits[b >>> 4 & 0xf]; 168 | resultCharArray[index++] = hexDigits[b & 0xf]; 169 | } 170 | // 字符数组组合成字符串返回 171 | return new String(resultCharArray); 172 | } 173 | 174 | public static String strMd5(String input) { 175 | if (input == null || input.length() == 0) { 176 | return null; 177 | } 178 | try { 179 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 180 | md5.update(input.getBytes()); 181 | byte[] byteArray = md5.digest(); 182 | 183 | BigInteger bigInt = new BigInteger(1, byteArray); 184 | // 参数16表示16进制 185 | String result = bigInt.toString(16); 186 | // 不足32位高位补零 187 | while (result.length() < 32) { 188 | result = "0" + result; 189 | } 190 | return result; 191 | } catch (NoSuchAlgorithmException e) { 192 | e.printStackTrace(); 193 | } 194 | return null; 195 | } 196 | 197 | public static final void ensurePathExist(String path) { 198 | File file = new File(path); 199 | if (!file.exists()) { 200 | file.mkdirs(); 201 | } 202 | } 203 | 204 | } 205 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/util/ReflectUtils.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.util; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.lang.reflect.Method; 6 | 7 | public class ReflectUtils { 8 | 9 | //获取类的实例的变量的值 10 | public static Object getField(Object receiver, String fieldName) { 11 | return getField(null, receiver, fieldName); 12 | } 13 | 14 | //获取类的静态变量的值 15 | public static Object getField(String className, String fieldName) { 16 | return getField(className,null, fieldName); 17 | } 18 | 19 | public static Object getField(Class clazz, String className, String fieldName, Object receiver) { 20 | try { 21 | if (clazz == null) clazz = Class.forName(className); 22 | Field field = clazz.getDeclaredField(fieldName); 23 | if (field == null) return null; 24 | field.setAccessible(true); 25 | return field.get(receiver); 26 | } catch (Throwable e) { 27 | e.printStackTrace(); 28 | } 29 | return null; 30 | } 31 | 32 | private static Object getField(String className, Object receiver, String fieldName) { 33 | Class clazz = null; 34 | Field field; 35 | if (!isEmpty(className)) { 36 | try { 37 | clazz = Class.forName(className); 38 | } catch (ClassNotFoundException e) { 39 | e.printStackTrace(); 40 | } 41 | } else { 42 | if (receiver != null) { 43 | clazz = receiver.getClass(); 44 | } 45 | } 46 | if (clazz == null) return null; 47 | 48 | try { 49 | field = findField(clazz, fieldName); 50 | if (field == null) return null; 51 | field.setAccessible(true); 52 | return field.get(receiver); 53 | } catch (IllegalAccessException e) { 54 | e.printStackTrace(); 55 | } catch (IllegalArgumentException e) { 56 | e.printStackTrace(); 57 | } catch (NullPointerException e) { 58 | e.printStackTrace(); 59 | } 60 | return null; 61 | } 62 | 63 | public static Object setField(Object receiver, String fieldName, Object value) { 64 | try { 65 | Field field; 66 | field = findField(receiver.getClass(), fieldName); 67 | if (field == null) { 68 | return null; 69 | } 70 | field.setAccessible(true); 71 | Object old = field.get(receiver); 72 | field.set(receiver, value); 73 | return old; 74 | } catch (IllegalAccessException e) { 75 | e.printStackTrace(); 76 | } catch (IllegalArgumentException e) { 77 | e.printStackTrace(); 78 | } 79 | return null; 80 | } 81 | 82 | public static Object setField(Class clazz, Object receiver, String fieldName, Object value) { 83 | try { 84 | Field field; 85 | field = findField(clazz, fieldName); 86 | if (field == null) { 87 | return null; 88 | } 89 | field.setAccessible(true); 90 | Object old = field.get(receiver); 91 | field.set(receiver, value); 92 | return old; 93 | } catch (IllegalAccessException e) { 94 | e.printStackTrace(); 95 | } catch (IllegalArgumentException e) { 96 | e.printStackTrace(); 97 | } 98 | return null; 99 | } 100 | 101 | public static Object setField(String clazzName, Object receiver, String fieldName, Object value){ 102 | try { 103 | Class clazz = Class.forName(clazzName); 104 | Field field; 105 | field = findField(clazz, fieldName); 106 | if (field == null) { 107 | return null; 108 | } 109 | field.setAccessible(true); 110 | Object old = field.get(receiver); 111 | field.set(receiver, value); 112 | return old; 113 | } catch (IllegalAccessException e) { 114 | e.printStackTrace(); 115 | } catch (IllegalArgumentException e) { 116 | e.printStackTrace(); 117 | } catch (ClassNotFoundException e) { 118 | e.printStackTrace(); 119 | } 120 | return null; 121 | } 122 | 123 | public static Object callMethod(String className, Object receiver, String methodName, Object... params) { 124 | Class clazz = null; 125 | if (!isEmpty(className)) { 126 | try { 127 | clazz = Class.forName(className); 128 | } catch (ClassNotFoundException e) { 129 | e.printStackTrace(); 130 | } 131 | } else { 132 | if (receiver != null) { 133 | clazz = receiver.getClass(); 134 | } 135 | } 136 | if (clazz == null) return null; 137 | try { 138 | Method method = findMethod(clazz, methodName, params); 139 | if (method == null) { 140 | return null; 141 | } 142 | method.setAccessible(true); 143 | return method.invoke(receiver, params); 144 | } catch (IllegalArgumentException e) { 145 | e.printStackTrace(); 146 | } catch (IllegalAccessException e) { 147 | e.printStackTrace(); 148 | } catch (InvocationTargetException e) { 149 | e.printStackTrace(); 150 | } 151 | return null; 152 | } 153 | 154 | private static Method findMethod(Class clazz, String name, Object... arg) { 155 | Method[] methods = clazz.getMethods(); 156 | Method method = null; 157 | for (Method m : methods) { 158 | if (methodFitParam(m, name, arg)) { 159 | method = m; 160 | break; 161 | } 162 | } 163 | 164 | if (method == null) { 165 | method = findDeclaredMethod(clazz, name, arg); 166 | } 167 | return method; 168 | } 169 | 170 | private static Method findDeclaredMethod(Class clazz, String name, Object... arg) { 171 | Method[] methods = clazz.getDeclaredMethods(); 172 | Method method = null; 173 | for (Method m : methods) { 174 | if (methodFitParam(m, name, arg)) { 175 | method = m; 176 | break; 177 | } 178 | } 179 | 180 | if (method == null) { 181 | if (clazz.equals(Object.class)) { 182 | return null; 183 | } 184 | return findDeclaredMethod(clazz.getSuperclass(), name, arg); 185 | } 186 | return method; 187 | } 188 | 189 | private static boolean methodFitParam(Method method, String methodName, Object... arg) { 190 | if (!methodName.equals(method.getName())) { 191 | return false; 192 | } 193 | 194 | Class[] paramTypes = method.getParameterTypes(); 195 | if (arg == null || arg.length == 0) { 196 | if (paramTypes == null || paramTypes.length == 0) { 197 | return true; 198 | } else { 199 | return false; 200 | } 201 | } 202 | if (paramTypes.length != arg.length) { 203 | return false; 204 | } 205 | 206 | for (int i = 0; i < arg.length; ++i) { 207 | Object ar = arg[i]; 208 | Class paramT = paramTypes[i]; 209 | if (ar == null) continue; 210 | 211 | //TODO for primitive type 212 | if (paramT.isPrimitive()) continue; 213 | 214 | if (!paramT.isInstance(ar)) { 215 | return false; 216 | } 217 | } 218 | return true; 219 | } 220 | 221 | private static Field findField(Class clazz, String name) { 222 | try { 223 | return clazz.getDeclaredField(name); 224 | } catch (NoSuchFieldException e) { 225 | if (clazz.equals(Object.class)) { 226 | e.printStackTrace(); 227 | return null; 228 | } 229 | Class base = clazz.getSuperclass(); 230 | return findField(base, name); 231 | } 232 | } 233 | 234 | private static boolean isEmpty(String str) { 235 | return str == null || str.length() == 0; 236 | } 237 | } -------------------------------------------------------------------------------- /core/src/main/java/android/app/AndroidAppHelper.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.content.SharedPreferences; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.res.CompatibilityInfo; 6 | import android.content.res.Configuration; 7 | import android.content.res.Resources; 8 | import android.os.Build; 9 | import android.os.IBinder; 10 | import android.view.Display; 11 | 12 | import java.lang.ref.WeakReference; 13 | import java.util.Map; 14 | 15 | import de.robv.android.xposed.XSharedPreferences; 16 | import de.robv.android.xposed.XposedBridge; 17 | 18 | import static de.robv.android.xposed.XposedHelpers.findClass; 19 | import static de.robv.android.xposed.XposedHelpers.findFieldIfExists; 20 | import static de.robv.android.xposed.XposedHelpers.findMethodExactIfExists; 21 | import static de.robv.android.xposed.XposedHelpers.getObjectField; 22 | import static de.robv.android.xposed.XposedHelpers.newInstance; 23 | import static de.robv.android.xposed.XposedHelpers.setFloatField; 24 | 25 | /** 26 | * Contains various methods for information about the current app. 27 | * 28 | *

For historical reasons, this class is in the {@code android.app} package. It can't be moved 29 | * without breaking compatibility with existing modules. 30 | */ 31 | public final class AndroidAppHelper { 32 | private AndroidAppHelper() {} 33 | 34 | private static final Class CLASS_RESOURCES_KEY; 35 | private static final boolean HAS_IS_THEMEABLE; 36 | private static final boolean HAS_THEME_CONFIG_PARAMETER; 37 | 38 | static { 39 | CLASS_RESOURCES_KEY = (Build.VERSION.SDK_INT < 19) ? 40 | findClass("android.app.ActivityThread$ResourcesKey", null) 41 | : findClass("android.content.res.ResourcesKey", null); 42 | 43 | HAS_IS_THEMEABLE = findFieldIfExists(CLASS_RESOURCES_KEY, "mIsThemeable") != null; 44 | HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && Build.VERSION.SDK_INT >= 21 45 | && findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null; 46 | } 47 | 48 | @SuppressWarnings({ "unchecked", "rawtypes" }) 49 | private static Map getResourcesMap(ActivityThread activityThread) { 50 | if (Build.VERSION.SDK_INT >= 24) { 51 | Object resourcesManager = getObjectField(activityThread, "mResourcesManager"); 52 | return (Map) getObjectField(resourcesManager, "mResourceImpls"); 53 | } else if (Build.VERSION.SDK_INT >= 19) { 54 | Object resourcesManager = getObjectField(activityThread, "mResourcesManager"); 55 | return (Map) getObjectField(resourcesManager, "mActiveResources"); 56 | } else { 57 | return (Map) getObjectField(activityThread, "mActiveResources"); 58 | } 59 | } 60 | 61 | /* For SDK 15 & 16 */ 62 | private static Object createResourcesKey(String resDir, float scale) { 63 | try { 64 | if (HAS_IS_THEMEABLE) 65 | return newInstance(CLASS_RESOURCES_KEY, resDir, scale, false); 66 | else 67 | return newInstance(CLASS_RESOURCES_KEY, resDir, scale); 68 | } catch (Throwable t) { 69 | XposedBridge.log(t); 70 | return null; 71 | } 72 | } 73 | 74 | /* For SDK 17 & 18 & 23 */ 75 | private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) { 76 | try { 77 | if (HAS_THEME_CONFIG_PARAMETER) 78 | return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null); 79 | else if (HAS_IS_THEMEABLE) 80 | return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false); 81 | else 82 | return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale); 83 | } catch (Throwable t) { 84 | XposedBridge.log(t); 85 | return null; 86 | } 87 | } 88 | 89 | /* For SDK 19 - 22 */ 90 | private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale, IBinder token) { 91 | try { 92 | if (HAS_THEME_CONFIG_PARAMETER) 93 | return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null, token); 94 | else if (HAS_IS_THEMEABLE) 95 | return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, token); 96 | else 97 | return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, token); 98 | } catch (Throwable t) { 99 | XposedBridge.log(t); 100 | return null; 101 | } 102 | } 103 | 104 | /* For SDK 24+ */ 105 | private static Object createResourcesKey(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, CompatibilityInfo compatInfo) { 106 | try { 107 | return newInstance(CLASS_RESOURCES_KEY, resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfiguration, compatInfo); 108 | } catch (Throwable t) { 109 | XposedBridge.log(t); 110 | return null; 111 | } 112 | } 113 | 114 | /** @hide */ 115 | public static void addActiveResource(String resDir, float scale, boolean isThemeable, Resources resources) { 116 | addActiveResource(resDir, resources); 117 | } 118 | 119 | /** @hide */ 120 | public static void addActiveResource(String resDir, Resources resources) { 121 | ActivityThread thread = ActivityThread.currentActivityThread(); 122 | if (thread == null) { 123 | return; 124 | } 125 | 126 | Object resourcesKey; 127 | if (Build.VERSION.SDK_INT >= 24) { 128 | CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class); 129 | setFloatField(compatInfo, "applicationScale", resources.hashCode()); 130 | resourcesKey = createResourcesKey(resDir, null, null, null, Display.DEFAULT_DISPLAY, null, compatInfo); 131 | } else if (Build.VERSION.SDK_INT == 23) { 132 | resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode()); 133 | } else if (Build.VERSION.SDK_INT >= 19) { 134 | resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode(), null); 135 | } else if (Build.VERSION.SDK_INT >= 17) { 136 | resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode()); 137 | } else { 138 | resourcesKey = createResourcesKey(resDir, resources.hashCode()); 139 | } 140 | 141 | if (resourcesKey != null) { 142 | if (Build.VERSION.SDK_INT >= 24) { 143 | Object resImpl = getObjectField(resources, "mResourcesImpl"); 144 | getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl)); 145 | } else { 146 | getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resources)); 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * Returns the name of the current process. It's usually the same as the main package name. 153 | */ 154 | public static String currentProcessName() { 155 | String processName = ActivityThread.currentPackageName(); 156 | if (processName == null) 157 | return "android"; 158 | return processName; 159 | } 160 | 161 | /** 162 | * Returns information about the main application in the current process. 163 | * 164 | *

In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the 165 | * Keyguard which both have {@code android:process="com.android.systemui"} set in their 166 | * manifest. In those cases, the first application that was initialized will be returned. 167 | */ 168 | public static ApplicationInfo currentApplicationInfo() { 169 | ActivityThread am = ActivityThread.currentActivityThread(); 170 | if (am == null) 171 | return null; 172 | 173 | Object boundApplication = getObjectField(am, "mBoundApplication"); 174 | if (boundApplication == null) 175 | return null; 176 | 177 | return (ApplicationInfo) getObjectField(boundApplication, "appInfo"); 178 | } 179 | 180 | /** 181 | * Returns the Android package name of the main application in the current process. 182 | * 183 | *

In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the 184 | * Keyguard which both have {@code android:process="com.android.systemui"} set in their 185 | * manifest. In those cases, the first application that was initialized will be returned. 186 | */ 187 | public static String currentPackageName() { 188 | ApplicationInfo ai = currentApplicationInfo(); 189 | return (ai != null) ? ai.packageName : "android"; 190 | } 191 | 192 | /** 193 | * Returns the main {@link Application} object in the current process. 194 | * 195 | *

In a few cases, multiple apps might run in the same process, e.g. the SystemUI and the 196 | * Keyguard which both have {@code android:process="com.android.systemui"} set in their 197 | * manifest. In those cases, the first application that was initialized will be returned. 198 | */ 199 | public static Application currentApplication() { 200 | return ActivityThread.currentApplication(); 201 | } 202 | 203 | /** @deprecated Use {@link XSharedPreferences} instead. */ 204 | @SuppressWarnings("UnusedParameters") 205 | @Deprecated 206 | public static SharedPreferences getSharedPreferencesForPackage(String packageName, String prefFileName, int mode) { 207 | return new XSharedPreferences(packageName, prefFileName); 208 | } 209 | 210 | /** @deprecated Use {@link XSharedPreferences} instead. */ 211 | @Deprecated 212 | public static SharedPreferences getDefaultSharedPreferencesForPackage(String packageName) { 213 | return new XSharedPreferences(packageName); 214 | } 215 | 216 | /** @deprecated Use {@link XSharedPreferences#reload} instead. */ 217 | @Deprecated 218 | public static void reloadSharedPreferencesIfNeeded(SharedPreferences pref) { 219 | if (pref instanceof XSharedPreferences) { 220 | ((XSharedPreferences) pref).reload(); 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/hooker/PackageSignatureHooker.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry.hooker; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.PackageManager; 6 | import android.content.pm.Signature; 7 | import android.os.Build; 8 | 9 | // import com.lody.whale.WhaleRuntime; 10 | import com.wind.xposed.entry.XposedModuleEntry; 11 | import com.wind.xposed.entry.util.FileUtils; 12 | 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.InvocationHandler; 15 | import java.lang.reflect.Method; 16 | import java.lang.reflect.Proxy; 17 | 18 | import de.robv.android.xposed.IXposedHookLoadPackage; 19 | import de.robv.android.xposed.XC_MethodHook; 20 | import de.robv.android.xposed.XposedHelpers; 21 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 22 | 23 | public class PackageSignatureHooker implements IXposedHookLoadPackage { 24 | 25 | private final static String SIGNATURE_INFO_ASSET_PATH = "xpatch_asset/original_signature_info.ini"; 26 | 27 | @Override 28 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { 29 | 30 | Context context = XposedModuleEntry.getAppContext(); 31 | final String originalSignature = getOriginalSignatureFromAsset(context); 32 | android.util.Log.d("PackageSignatureHooker", "Get the original signature --> " + originalSignature); 33 | if (originalSignature == null || originalSignature.isEmpty()) { 34 | return; 35 | } 36 | 37 | // hookSignatureByXposed(lpparam, originalSignature); //不稳定 暂时不使用 38 | hookSignatureByProxy(lpparam, originalSignature, context); 39 | 40 | } 41 | 42 | private void hookSignatureByXposed(XC_LoadPackage.LoadPackageParam lpparam, final String originalSignature) { 43 | final String currentPackageName = lpparam.packageName; 44 | 45 | XposedHelpers.findAndHookMethod("android.app.ApplicationPackageManager", lpparam.classLoader, 46 | "getPackageInfo", String.class, int.class, 47 | new XC_MethodHook() { 48 | @Override 49 | protected void afterHookedMethod(MethodHookParam param) throws Throwable { 50 | try { 51 | int flag = (int) param.args[1]; 52 | PackageInfo packageInfo = (PackageInfo) param.getResult(); 53 | android.util.Log.d("PackageSignatureHooker", "Get flag " + flag + " packageInfo =" + 54 | packageInfo); 55 | 56 | if (PackageManager.GET_SIGNATURES == flag) { 57 | if (param.args[0] != null && param.args[0] instanceof String) { 58 | String packageName = (String) param.args[0]; 59 | if (!packageName.equals(currentPackageName)) { 60 | return; 61 | } 62 | } 63 | 64 | // 先获取这个方法返回的结果 65 | if (packageInfo.signatures != null && packageInfo.signatures.length > 0) { 66 | android.util.Log.d("PackageSignatureHooker", "ackageInfo.signatures " + packageInfo.signatures 67 | + " packageInfo =" + 68 | packageInfo); 69 | // 替换结果里的签名信息 70 | packageInfo.signatures[0] = new Signature(originalSignature); 71 | } 72 | } else if (Build.VERSION.SDK_INT >= 28 && PackageManager.GET_SIGNING_CERTIFICATES == flag) { 73 | if (param.args[0] != null && param.args[0] instanceof String) { 74 | String packageName = (String) param.args[0]; 75 | if (!packageName.equals(currentPackageName)) { 76 | return; 77 | } 78 | } 79 | 80 | if (packageInfo.signingInfo != null) { 81 | Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners(); 82 | if (signaturesArray != null && signaturesArray.length > 0) { 83 | signaturesArray[0] = new Signature(originalSignature); 84 | } 85 | } 86 | } 87 | // 更改最终的返回结果 88 | param.setResult(packageInfo); 89 | } catch (Throwable e) { 90 | e.printStackTrace(); 91 | android.util.Log.e("PackageSignatureHooker", "Get the original signature " + 92 | " failed !!!!!!!! ", e); 93 | } 94 | } 95 | }); 96 | } 97 | 98 | private void hookSignatureByProxy(XC_LoadPackage.LoadPackageParam lpparam, String originalSignature, Context context) { 99 | try { 100 | 101 | // Just make sure whale so is loaded, the the hidden policy is disabled. 102 | // WhaleRuntime.reserved2(); 103 | 104 | Class activityThreadClass = Class.forName("android.app.ActivityThread"); 105 | Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); 106 | // 获取全局的ActivityThread对象的实现 107 | Object activityThreadObj = currentActivityThreadMethod.invoke(null); 108 | 109 | // 获取ActivityThread里的getPackageManager方法获取原始的sPackageManager对象 110 | Method getPackageManagerMethod = activityThreadClass.getDeclaredMethod("getPackageManager"); 111 | getPackageManagerMethod.setAccessible(true); 112 | Object packageManagerObj = getPackageManagerMethod.invoke(activityThreadObj); 113 | 114 | // 准备好代理对象, 用来替换原始的对象 115 | Class iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager"); 116 | Object proxy = Proxy.newProxyInstance( 117 | iPackageManagerInterface.getClassLoader(), 118 | new Class[]{iPackageManagerInterface}, 119 | new MyInvocationHandler(packageManagerObj, lpparam.packageName, originalSignature)); 120 | // 1. 替换掉ActivityThread里面的 sPackageManager 字段 121 | Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager"); 122 | sPackageManagerField.setAccessible(true); 123 | sPackageManagerField.set(activityThreadObj, proxy); 124 | // 2. 替换 ApplicationPackageManager里面的 mPM对象 125 | PackageManager pm = context.getPackageManager(); 126 | Field mPmField = pm.getClass().getDeclaredField("mPM"); 127 | mPmField.setAccessible(true); 128 | mPmField.set(pm, proxy); 129 | } catch (Exception e) { 130 | android.util.Log.e("PackageSignatureHooker", " hookSignatureByProxy failed !!", e); 131 | } 132 | } 133 | 134 | static class MyInvocationHandler implements InvocationHandler { 135 | 136 | private Object pmBase; 137 | private String currentPackageName; 138 | private String originalSignature; 139 | 140 | public MyInvocationHandler(Object base, String currentPackageName, String originalSignature) { 141 | pmBase = base; 142 | this.currentPackageName = currentPackageName; 143 | this.originalSignature = originalSignature; 144 | } 145 | 146 | @Override 147 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 148 | if ("getPackageInfo".equals(method.getName())) { 149 | if (args[0] != null && args[0] instanceof String) { 150 | String packageName = (String) args[0]; 151 | if (!packageName.equals(currentPackageName)) { 152 | return method.invoke(pmBase, args); 153 | } 154 | } 155 | 156 | Integer flag = (Integer) args[1]; 157 | if (PackageManager.GET_SIGNATURES == flag) { 158 | PackageInfo packageInfo = (PackageInfo) method.invoke(pmBase, args); 159 | 160 | // 先获取这个方法返回的结果 161 | if (packageInfo.signatures != null && packageInfo.signatures.length > 0) { 162 | // 替换结果里的签名信息 163 | packageInfo.signatures[0] = new Signature(originalSignature); 164 | } 165 | return packageInfo; 166 | } else if (Build.VERSION.SDK_INT >= 28 && PackageManager.GET_SIGNING_CERTIFICATES == flag) { 167 | PackageInfo packageInfo = (PackageInfo) method.invoke(pmBase, args); 168 | if (packageInfo.signingInfo != null) { 169 | Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners(); 170 | if (signaturesArray != null && signaturesArray.length > 0) { 171 | signaturesArray[0] = new Signature(originalSignature); 172 | } 173 | } 174 | return packageInfo; 175 | } 176 | } 177 | return method.invoke(pmBase, args); 178 | } 179 | } 180 | 181 | private String getOriginalSignatureFromAsset(Context context) { 182 | return FileUtils.readTextFromAssets(context, SIGNATURE_INFO_ASSET_PATH); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /core/src/main/java/com/wind/xposed/entry/XposedModuleEntry.java: -------------------------------------------------------------------------------- 1 | package com.wind.xposed.entry; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.Build; 8 | import android.os.Environment; 9 | import android.text.TextUtils; 10 | import android.util.Log; 11 | import android.util.Pair; 12 | 13 | import com.wind.xposed.entry.util.FileUtils; 14 | import com.wind.xposed.entry.util.NativeLibraryHelperCompat; 15 | import com.wind.xposed.entry.util.PackageNameCache; 16 | import com.wind.xposed.entry.util.PluginNativeLibExtractor; 17 | import com.wind.xposed.entry.util.SharedPrefUtils; 18 | import com.wind.xposed.entry.util.VMRuntime; 19 | import com.wind.xposed.entry.util.XLog; 20 | import com.wind.xposed.entry.util.XpatchUtils; 21 | 22 | import java.io.BufferedReader; 23 | import java.io.BufferedWriter; 24 | import java.io.Closeable; 25 | import java.io.File; 26 | import java.io.FileInputStream; 27 | import java.io.FileOutputStream; 28 | import java.io.IOException; 29 | import java.io.InputStreamReader; 30 | import java.io.OutputStreamWriter; 31 | import java.util.ArrayList; 32 | import java.util.Collection; 33 | import java.util.List; 34 | import java.util.concurrent.atomic.AtomicBoolean; 35 | 36 | import de.robv.android.xposed.XposedHelper; 37 | 38 | /** 39 | * Created by Wind 40 | */ 41 | public class XposedModuleEntry { 42 | 43 | private static final String TAG = "XposedModuleEntry"; 44 | 45 | private static AtomicBoolean hasInited = new AtomicBoolean(false); 46 | private static Context appContext; 47 | 48 | public static void init(Context context) { 49 | init(context, null, true); 50 | } 51 | 52 | public static void init(Context context, List moduleFilePathList) { 53 | init(context, moduleFilePathList, false); 54 | } 55 | 56 | public static void init(Context context, String fileDir) { 57 | File fileParent = new File(fileDir); 58 | if (!fileParent.exists()) { 59 | return; 60 | } 61 | 62 | List modulePathList = new ArrayList<>(); 63 | File[] childFileList = fileParent.listFiles(); 64 | if (childFileList != null && childFileList.length > 0) { 65 | for (File file : childFileList) { 66 | if (file.isFile()) { 67 | modulePathList.add(file.getAbsolutePath()); 68 | } 69 | } 70 | } 71 | init(context, modulePathList, false); 72 | } 73 | 74 | public static void init(Context context, List modulePathList, boolean enableLoadInstalledModules) { 75 | if (!hasInited.compareAndSet(false, true)) { 76 | Log.e(TAG, " Xposed module has been loaded !!!"); 77 | return; 78 | } 79 | 80 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 81 | VMRuntime.setHiddenApiExemptions(new String[]{"L"}); 82 | } 83 | if (context == null) { 84 | context = XpatchUtils.createAppContext(); 85 | } 86 | SandHookInitialization.init(context); 87 | 88 | if ((modulePathList == null || modulePathList.size() == 0) && !enableLoadInstalledModules) { 89 | Log.e(TAG, " modulePathList is null and installed module is disabled, so no xposed modules will be loaded."); 90 | return; 91 | } 92 | loadModulesInternal(context, modulePathList, enableLoadInstalledModules); 93 | } 94 | 95 | private static void loadModulesInternal(Context context, List userModulePathList, boolean enableLoadInstalledModules) { 96 | 97 | if (context == null) { 98 | android.util.Log.e(TAG, "try to init XposedModuleEntry, but create app context failed !!!!"); 99 | return; 100 | } 101 | 102 | appContext = context; 103 | 104 | SharedPrefUtils.init(context); 105 | ClassLoader originClassLoader = context.getClassLoader(); 106 | List modulePathList = new ArrayList<>(); 107 | List installedModulePathList = null; 108 | if (enableLoadInstalledModules) { 109 | installedModulePathList = loadAllInstalledModule(context); 110 | } 111 | 112 | if (userModulePathList != null && userModulePathList.size() > 0) { 113 | for (String path : userModulePathList) { 114 | if (path != null && path.length() > 0 && new File(path).exists()) { 115 | String packageName = getPackageNameByPath(context, path); 116 | if (packageName != null && packageName.length() > 0) { 117 | modulePathList.add(path); 118 | } else { 119 | Log.e(TAG, " Xposed module file path is invalid. The file is not an apk file!!!, path -> " + path); 120 | } 121 | } else { 122 | Log.e(TAG, " Xposed module file path is invalid!!! path -> " + path); 123 | } 124 | } 125 | } 126 | 127 | // 过滤掉已经打包在apk中的module,避免同一个module被加载了两次 128 | if (installedModulePathList != null && installedModulePathList.size() > 0) { 129 | List packedModulePakcageNameList = null; 130 | 131 | for (String apkPath : modulePathList) { 132 | if (packedModulePakcageNameList == null) { 133 | packedModulePakcageNameList = new ArrayList<>(); 134 | } 135 | String packageName = getPackageNameByPath(context, apkPath); 136 | XLog.d(TAG, "Current packed module path ----> " + apkPath + " packageName = " + packageName); 137 | packedModulePakcageNameList.add(packageName); 138 | } 139 | 140 | if (packedModulePakcageNameList == null || packedModulePakcageNameList.size() == 0) { 141 | modulePathList.addAll(installedModulePathList); 142 | } else { 143 | for (String apkPath : installedModulePathList) { 144 | String packageName = getPackageNameByPath(context, apkPath); 145 | XLog.d(TAG, "Current installed module path ----> " + apkPath + " packageName = " + packageName); 146 | if (!packedModulePakcageNameList.contains(packageName)) { 147 | modulePathList.add(apkPath); 148 | } 149 | } 150 | } 151 | } 152 | 153 | String appPrivateDir = context.getFilesDir().getParentFile().getAbsolutePath(); 154 | for (String modulePath : modulePathList) { 155 | String dexPath = context.getDir("xposed_plugin_dex", Context.MODE_PRIVATE).getAbsolutePath(); 156 | if (!TextUtils.isEmpty(modulePath)) { 157 | String packageName = getPackageNameByPath(context, modulePath); 158 | Log.d(TAG, "Current truely loaded module path ----> " + modulePath + " packageName: " + packageName); 159 | String pathNameSuffix = packageName; 160 | if (pathNameSuffix == null || pathNameSuffix.isEmpty()) { 161 | pathNameSuffix = XpatchUtils.strMd5(modulePath); 162 | } 163 | 164 | String xposedPluginFilePath = appPrivateDir + "/xpatch_plugin_native_lib/plugin_" + pathNameSuffix; 165 | 166 | String soFilePath; 167 | if (NativeLibraryHelperCompat.is64bit()) { 168 | soFilePath = xposedPluginFilePath + "/lib/arm64"; 169 | } else { 170 | soFilePath = xposedPluginFilePath + "/lib/arm"; 171 | } 172 | // 将插件apk中的so文件释放到soFilePath目录下 173 | PluginNativeLibExtractor.copySoFileIfNeeded(context, soFilePath, modulePath); 174 | 175 | XposedModuleLoader.loadModule(modulePath, dexPath, soFilePath, context.getApplicationInfo(), originClassLoader); 176 | } 177 | } 178 | } 179 | 180 | private static String getPackageNameByPath(Context context, String apkPath) { 181 | return PackageNameCache.getInstance(context).getPackageNameByPath(apkPath); 182 | } 183 | 184 | private static List loadAllInstalledModule(Context context) { 185 | PackageManager pm = context.getPackageManager(); 186 | List modulePathList = new ArrayList<>(); 187 | List packageInfoList = pm.getInstalledPackages(PackageManager.GET_META_DATA); 188 | for (PackageInfo pkg : packageInfoList) { 189 | ApplicationInfo app = pkg.applicationInfo; 190 | if (!app.enabled) 191 | continue; 192 | if (app.metaData != null && app.metaData.containsKey("xposedmodule")) { 193 | String apkPath = pkg.applicationInfo.publicSourceDir; 194 | if (TextUtils.isEmpty(apkPath)) { 195 | apkPath = pkg.applicationInfo.sourceDir; 196 | } 197 | if (!TextUtils.isEmpty(apkPath)) { 198 | XLog.d(TAG, " query installed module path -> " + apkPath); 199 | modulePathList.add(apkPath); 200 | } 201 | } 202 | } 203 | return modulePathList; 204 | } 205 | 206 | private static void closeStream(Closeable closeable) { 207 | if (closeable != null) { 208 | try { 209 | closeable.close(); 210 | } catch (IOException e) { 211 | e.printStackTrace(); 212 | } 213 | } 214 | } 215 | 216 | private static boolean isEmpty(Collection collection) { 217 | if (collection == null || collection.size() == 0) { 218 | return true; 219 | } 220 | return false; 221 | } 222 | 223 | public static Context getAppContext() { 224 | return appContext; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 WindySha 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | 6 | Unless required by applicable law or agreed to in writing, software 7 | distributed under the License is distributed on an "AS IS" BASIS, 8 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9 | See the License for the specific language governing permissions and 10 | limitations under the License. 11 | 12 | 13 | Apache License 14 | Version 2.0, January 2004 15 | http://www.apache.org/licenses/ 16 | 17 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 18 | 19 | 1. Definitions. 20 | 21 | "License" shall mean the terms and conditions for use, reproduction, 22 | and distribution as defined by Sections 1 through 9 of this document. 23 | 24 | "Licensor" shall mean the copyright owner or entity authorized by 25 | the copyright owner that is granting the License. 26 | 27 | "Legal Entity" shall mean the union of the acting entity and all 28 | other entities that control, are controlled by, or are under common 29 | control with that entity. For the purposes of this definition, 30 | "control" means (i) the power, direct or indirect, to cause the 31 | direction or management of such entity, whether by contract or 32 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 33 | outstanding shares, or (iii) beneficial ownership of such entity. 34 | 35 | "You" (or "Your") shall mean an individual or Legal Entity 36 | exercising permissions granted by this License. 37 | 38 | "Source" form shall mean the preferred form for making modifications, 39 | including but not limited to software source code, documentation 40 | source, and configuration files. 41 | 42 | "Object" form shall mean any form resulting from mechanical 43 | transformation or translation of a Source form, including but 44 | not limited to compiled object code, generated documentation, 45 | and conversions to other media types. 46 | 47 | "Work" shall mean the work of authorship, whether in Source or 48 | Object form, made available under the License, as indicated by a 49 | copyright notice that is included in or attached to the work 50 | (an example is provided in the Appendix below). 51 | 52 | "Derivative Works" shall mean any work, whether in Source or Object 53 | form, that is based on (or derived from) the Work and for which the 54 | editorial revisions, annotations, elaborations, or other modifications 55 | represent, as a whole, an original work of authorship. For the purposes 56 | of this License, Derivative Works shall not include works that remain 57 | separable from, or merely link (or bind by name) to the interfaces of, 58 | the Work and Derivative Works thereof. 59 | 60 | "Contribution" shall mean any work of authorship, including 61 | the original version of the Work and any modifications or additions 62 | to that Work or Derivative Works thereof, that is intentionally 63 | submitted to Licensor for inclusion in the Work by the copyright owner 64 | or by an individual or Legal Entity authorized to submit on behalf of 65 | the copyright owner. For the purposes of this definition, "submitted" 66 | means any form of electronic, verbal, or written communication sent 67 | to the Licensor or its representatives, including but not limited to 68 | communication on electronic mailing lists, source code control systems, 69 | and issue tracking systems that are managed by, or on behalf of, the 70 | Licensor for the purpose of discussing and improving the Work, but 71 | excluding communication that is conspicuously marked or otherwise 72 | designated in writing by the copyright owner as "Not a Contribution." 73 | 74 | "Contributor" shall mean Licensor and any individual or Legal Entity 75 | on behalf of whom a Contribution has been received by Licensor and 76 | subsequently incorporated within the Work. 77 | 78 | 2. Grant of Copyright License. Subject to the terms and conditions of 79 | this License, each Contributor hereby grants to You a perpetual, 80 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 81 | copyright license to reproduce, prepare Derivative Works of, 82 | publicly display, publicly perform, sublicense, and distribute the 83 | Work and such Derivative Works in Source or Object form. 84 | 85 | 3. Grant of Patent License. Subject to the terms and conditions of 86 | this License, each Contributor hereby grants to You a perpetual, 87 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 88 | (except as stated in this section) patent license to make, have made, 89 | use, offer to sell, sell, import, and otherwise transfer the Work, 90 | where such license applies only to those patent claims licensable 91 | by such Contributor that are necessarily infringed by their 92 | Contribution(s) alone or by combination of their Contribution(s) 93 | with the Work to which such Contribution(s) was submitted. If You 94 | institute patent litigation against any entity (including a 95 | cross-claim or counterclaim in a lawsuit) alleging that the Work 96 | or a Contribution incorporated within the Work constitutes direct 97 | or contributory patent infringement, then any patent licenses 98 | granted to You under this License for that Work shall terminate 99 | as of the date such litigation is filed. 100 | 101 | 4. Redistribution. You may reproduce and distribute copies of the 102 | Work or Derivative Works thereof in any medium, with or without 103 | modifications, and in Source or Object form, provided that You 104 | meet the following conditions: 105 | 106 | (a) You must give any other recipients of the Work or 107 | Derivative Works a copy of this License; and 108 | 109 | (b) You must cause any modified files to carry prominent notices 110 | stating that You changed the files; and 111 | 112 | (c) You must retain, in the Source form of any Derivative Works 113 | that You distribute, all copyright, patent, trademark, and 114 | attribution notices from the Source form of the Work, 115 | excluding those notices that do not pertain to any part of 116 | the Derivative Works; and 117 | 118 | (d) If the Work includes a "NOTICE" text file as part of its 119 | distribution, then any Derivative Works that You distribute must 120 | include a readable copy of the attribution notices contained 121 | within such NOTICE file, excluding those notices that do not 122 | pertain to any part of the Derivative Works, in at least one 123 | of the following places: within a NOTICE text file distributed 124 | as part of the Derivative Works; within the Source form or 125 | documentation, if provided along with the Derivative Works; or, 126 | within a display generated by the Derivative Works, if and 127 | wherever such third-party notices normally appear. The contents 128 | of the NOTICE file are for informational purposes only and 129 | do not modify the License. You may add Your own attribution 130 | notices within Derivative Works that You distribute, alongside 131 | or as an addendum to the NOTICE text from the Work, provided 132 | that such additional attribution notices cannot be construed 133 | as modifying the License. 134 | 135 | You may add Your own copyright statement to Your modifications and 136 | may provide additional or different license terms and conditions 137 | for use, reproduction, or distribution of Your modifications, or 138 | for any such Derivative Works as a whole, provided Your use, 139 | reproduction, and distribution of the Work otherwise complies with 140 | the conditions stated in this License. 141 | 142 | 5. Submission of Contributions. Unless You explicitly state otherwise, 143 | any Contribution intentionally submitted for inclusion in the Work 144 | by You to the Licensor shall be under the terms and conditions of 145 | this License, without any additional terms or conditions. 146 | Notwithstanding the above, nothing herein shall supersede or modify 147 | the terms of any separate license agreement you may have executed 148 | with Licensor regarding such Contributions. 149 | 150 | 6. Trademarks. This License does not grant permission to use the trade 151 | names, trademarks, service marks, or product names of the Licensor, 152 | except as required for reasonable and customary use in describing the 153 | origin of the Work and reproducing the content of the NOTICE file. 154 | 155 | 7. Disclaimer of Warranty. Unless required by applicable law or 156 | agreed to in writing, Licensor provides the Work (and each 157 | Contributor provides its Contributions) on an "AS IS" BASIS, 158 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 159 | implied, including, without limitation, any warranties or conditions 160 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 161 | PARTICULAR PURPOSE. You are solely responsible for determining the 162 | appropriateness of using or redistributing the Work and assume any 163 | risks associated with Your exercise of permissions under this License. 164 | 165 | 8. Limitation of Liability. In no event and under no legal theory, 166 | whether in tort (including negligence), contract, or otherwise, 167 | unless required by applicable law (such as deliberate and grossly 168 | negligent acts) or agreed to in writing, shall any Contributor be 169 | liable to You for damages, including any direct, indirect, special, 170 | incidental, or consequential damages of any character arising as a 171 | result of this License or out of the use or inability to use the 172 | Work (including but not limited to damages for loss of goodwill, 173 | work stoppage, computer failure or malfunction, or any and all 174 | other commercial damages or losses), even if such Contributor 175 | has been advised of the possibility of such damages. 176 | 177 | 9. Accepting Warranty or Additional Liability. While redistributing 178 | the Work or Derivative Works thereof, You may choose to offer, 179 | and charge a fee for, acceptance of support, warranty, indemnity, 180 | or other liability obligations and/or rights consistent with this 181 | License. However, in accepting such obligations, You may act only 182 | on Your own behalf and on Your sole responsibility, not on behalf 183 | of any other Contributor, and only if You agree to indemnify, 184 | defend, and hold each Contributor harmless for any liability 185 | incurred by, or claims asserted against, such Contributor by reason 186 | of your accepting any such warranty or additional liability. 187 | 188 | END OF TERMS AND CONDITIONS --------------------------------------------------------------------------------