diff --git a/loader/.gitignore b/loader/.gitignore new file mode 100644 index 0000000..040a5f3 --- /dev/null +++ b/loader/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +/out +/.idea diff --git a/loader/.gitmodules b/loader/.gitmodules new file mode 100644 index 0000000..d693ba6 --- /dev/null +++ b/loader/.gitmodules @@ -0,0 +1,4 @@ +[submodule "mmp"] + path = mmp + url = https://github.com/MMPosed/MMPosed.git + branch = mmpatch \ No newline at end of file diff --git a/loader/README.md b/loader/README.md new file mode 100644 index 0000000..335b4c3 --- /dev/null +++ b/loader/README.md @@ -0,0 +1,17 @@ +# Readme +LSPosed as hook framework + +There some major change since xpatch + +1. use LSPosed as hook framework +1. keep loader simple, clear not nesseacry things, like bypass signature. let developer do this part + + +`Maybe perform force push if some private data leak in project. Sorry for the confusion.` + +# Useage +1. You need do signature bypass by yourself +1. Orignal signature saved to assets/original_signature_info.ini +1. Orignal apk saved to assets/original_apk.bin + +For example, you may need to replace signatures from getPackageInfo and redirect `/data/app/{your apk}/base.apk` to `original_apk.bin` to bypass normally signature check. \ No newline at end of file diff --git a/loader/app/.gitignore b/loader/app/.gitignore new file mode 100644 index 0000000..c3dca1b --- /dev/null +++ b/loader/app/.gitignore @@ -0,0 +1,2 @@ +/build +/target diff --git a/loader/app/build.gradle b/loader/app/build.gradle new file mode 100644 index 0000000..b445e4d --- /dev/null +++ b/loader/app/build.gradle @@ -0,0 +1,71 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 30 + defaultConfig { + applicationId "com.storm.wind.xposed" + minSdkVersion 27 + targetSdkVersion 28 + versionCode version_code as Integer + versionName version_name + + multiDexEnabled false + + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a' + } + } + buildTypes { + debug { + debuggable true + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + release { + debuggable false + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } + applicationVariants.all { variant -> + def buildType = variant.name.capitalize() + def variantLowered = variant.name.toLowerCase() + + variant.outputs.all { + outputFileName = "${variant.getFlavorName()}-${variant.versionName}.apk" + } + + task "copyDex$buildType"(type: Copy) { + dependsOn("assemble$buildType") + def dexFilePath = "$buildDir/intermediates/dex/${variantLowered}/mergeDex${buildType}/classes.dex" + from dexFilePath + rename "(.*).dex", "classes-${version_name}.dex" + into "$rootProject.projectDir/out/list-dex" + } + + task "copySo$buildType"(type: Copy) { + dependsOn("assemble$buildType") + from "$buildDir/intermediates/merged_native_libs/${variantLowered}/out/lib" + into "$rootProject.projectDir/out/list-so" + } + + task "copy$buildType"() { + dependsOn("copySo$buildType") + dependsOn("copyDex$buildType") + + doLast { + System.out.println("Dex and so files has been copy to ${rootProject.projectDir}${File.separator}out") + } + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(':xpatchcore') + implementation("androidx.core:core:1.3.2") + implementation project(path: ':lspcore') +} diff --git a/loader/app/proguard-rules.pro b/loader/app/proguard-rules.pro new file mode 100644 index 0000000..1affd07 --- /dev/null +++ b/loader/app/proguard-rules.pro @@ -0,0 +1,26 @@ +-keep class com.wind.xposed.entry.MMPEntry { + public (); + public void initAndLoadModules(); +} + +-keep class com.wind.xpatch.proxy.**{*;} + +-keep class de.robv.android.xposed.**{*;} + +-keep class android.app.**{*;} +-keep class android.content.**{*;} +-keep class android.os.**{*;} + +-keep class android.view.**{*;} +-keep class com.lody.whale.**{*;} +-keep class com.android.internal.**{*;} +-keep class xposed.dummy.**{*;} +-keep class com.wind.xposed.entry.util.**{*;} + +-keep class com.swift.sandhook.**{*;} +-keep class com.swift.sandhook.xposedcompat.**{*;} + +-dontwarn android.content.res.Resources +-dontwarn android.content.res.Resources$Theme +-dontwarn android.content.res.AssetManager +-dontwarn android.content.res.TypedArray diff --git a/loader/app/src/main/AndroidManifest.xml b/loader/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..51b7de7 --- /dev/null +++ b/loader/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/loader/app/src/main/assets/original_signature_info.ini b/loader/app/src/main/assets/original_signature_info.ini new file mode 100644 index 0000000..8d3701e --- /dev/null +++ b/loader/app/src/main/assets/original_signature_info.ini @@ -0,0 +1 @@ +palceholder \ No newline at end of file diff --git a/loader/app/src/main/java/com/storm/wind/xposed/MainActivity.java b/loader/app/src/main/java/com/storm/wind/xposed/MainActivity.java new file mode 100644 index 0000000..d607b65 --- /dev/null +++ b/loader/app/src/main/java/com/storm/wind/xposed/MainActivity.java @@ -0,0 +1,61 @@ +package com.storm.wind.xposed; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.widget.TextView; + +import androidx.core.app.ActivityCompat; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedHelpers; + +//import android.support.v4.app.ActivityCompat; + +public class MainActivity extends Activity { + + private static final int REQUEST_PERMISSION_CODE = 1; + private static String[] PERMISSIONS_STORAGE = { + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + + @SuppressLint("SetTextI18n") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, PERMISSIONS_STORAGE, REQUEST_PERMISSION_CODE); + } + } + + XposedHelpers.findAndHookMethod(this.getClass(), "checkXposed", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + param.setResult(true); + } + }); + + TextView textView = findViewById(R.id.msg); + if (checkXposed()) { + textView.setText("ok"); + } + else { + textView.setText("fail"); + } + } + + public void onClick(View view) { + } + + public boolean checkXposed() { + return false; + } +} diff --git a/loader/app/src/main/java/com/storm/wind/xposed/XposedApplication.java b/loader/app/src/main/java/com/storm/wind/xposed/XposedApplication.java new file mode 100644 index 0000000..8b59e1c --- /dev/null +++ b/loader/app/src/main/java/com/storm/wind/xposed/XposedApplication.java @@ -0,0 +1,24 @@ +package com.storm.wind.xposed; + +import static com.wind.xposed.entry.MMPLoader.initAndLoadModules; + +import android.app.Application; +import android.content.Context; + +import org.lsposed.lspd.yahfa.hooker.YahfaHooker; + +import de.robv.android.xposed.XposedInit; + +public class XposedApplication extends Application { + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + } + + static { + System.loadLibrary("lspd"); + YahfaHooker.init(); + XposedInit.startsSystemServer = false; + initAndLoadModules(); + } +} diff --git a/loader/app/src/main/res/layout/activity_main.xml b/loader/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..deafab4 --- /dev/null +++ b/loader/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/loader/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/loader/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..b0907ca Binary files /dev/null and b/loader/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/loader/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/loader/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d8ae031 Binary files /dev/null and b/loader/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/loader/app/src/main/res/values/strings.xml b/loader/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..0ff0fd6 --- /dev/null +++ b/loader/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + + Xposed Module Loader + \ No newline at end of file diff --git a/loader/build.gradle b/loader/build.gradle new file mode 100644 index 0000000..88a5f86 --- /dev/null +++ b/loader/build.gradle @@ -0,0 +1,42 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + repositories { + google() + mavenCentral() + maven { url "https://jcenter.bintray.com" } + maven { url "https://jitpack.io" } + } + dependencies { + classpath 'com.android.tools.build:gradle:7.0.0-alpha12' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30" + } +} + +ext { + androidCompileSdkVersion = 30 + androidCompileNdkVersion = "22.1.7171670" + androidBuildToolsVersion = "30.0.3" + androidMinSdkVersion = 27 + androidTargetSdkVersion = 28 + verCode = 1 + verName = "mmpatch" + apiCode = 93 + defaultManagerPackageName = "org.github.mmpatch" + androidSourceCompatibility = JavaVersion.VERSION_11 + androidTargetCompatibility = JavaVersion.VERSION_11 + zipPathMagiskReleasePath = project(":lspcore").projectDir.path + "/build/tmp/release/magisk/" +} + +allprojects { + repositories { + google() + mavenCentral() + maven { url "https://jcenter.bintray.com" } + maven { url "https://jitpack.io" } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/loader/core/.gitignore b/loader/core/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/loader/core/.gitignore @@ -0,0 +1 @@ +/build diff --git a/loader/core/build.gradle b/loader/core/build.gradle new file mode 100644 index 0000000..fe502dd --- /dev/null +++ b/loader/core/build.gradle @@ -0,0 +1,29 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 30 + + defaultConfig { + minSdkVersion rootProject.ext.androidMinSdkVersion + targetSdkVersion rootProject.ext.androidTargetSdkVersion + versionCode 1 + versionName "1.0" + } + buildTypes { + debug { + debuggable true + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + release { + debuggable false + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation project(':lspcore') +} \ No newline at end of file diff --git a/loader/core/proguard-rules.pro b/loader/core/proguard-rules.pro new file mode 100644 index 0000000..6045eb7 --- /dev/null +++ b/loader/core/proguard-rules.pro @@ -0,0 +1,16 @@ +#-keep class com.wind.xposed.entry.XposedModuleEntry { +# public (); +# public void init(); +#} +#-keep class de.robv.android.xposed.**{*;} +#-keep class com.swift.sandhook.**{*;} +#-keep class com.swift.sandhook.xposedcompat.**{*;} +# +#-dontwarn de.robv.android.xposed.XposedHelper +-keep class com.wind.xposed.entry.MMPEntry { + public (); + public void initAndLoadModules(); +} +-keep class de.robv.android.xposed.**{*;} + +-dontwarn de.robv.android.xposed.XposedHelper \ No newline at end of file diff --git a/loader/core/src/main/AndroidManifest.xml b/loader/core/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e3574ff --- /dev/null +++ b/loader/core/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/loader/core/src/main/java/android/app/ActivityThread.java b/loader/core/src/main/java/android/app/ActivityThread.java new file mode 100644 index 0000000..8a5ede4 --- /dev/null +++ b/loader/core/src/main/java/android/app/ActivityThread.java @@ -0,0 +1,22 @@ +package android.app; + +import android.content.pm.ApplicationInfo; +import android.content.res.CompatibilityInfo; + +public final class ActivityThread { + public static ActivityThread currentActivityThread() { + throw new UnsupportedOperationException("STUB"); + } + + public static Application currentApplication() { + throw new UnsupportedOperationException("STUB"); + } + + public static String currentPackageName() { + throw new UnsupportedOperationException("STUB"); + } + + public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { + throw new UnsupportedOperationException("STUB"); + } +} diff --git a/loader/core/src/main/java/android/app/LoadedApk.java b/loader/core/src/main/java/android/app/LoadedApk.java new file mode 100644 index 0000000..856bedf --- /dev/null +++ b/loader/core/src/main/java/android/app/LoadedApk.java @@ -0,0 +1,21 @@ +package android.app; + +import android.content.pm.ApplicationInfo; + +public final class LoadedApk { + public ApplicationInfo getApplicationInfo() { + throw new UnsupportedOperationException("STUB"); + } + + public ClassLoader getClassLoader() { + throw new UnsupportedOperationException("STUB"); + } + + public String getPackageName() { + throw new UnsupportedOperationException("STUB"); + } + + public String getResDir() { + throw new UnsupportedOperationException("STUB"); + } +} diff --git a/loader/core/src/main/java/android/content/res/CompatibilityInfo.java b/loader/core/src/main/java/android/content/res/CompatibilityInfo.java new file mode 100644 index 0000000..9352367 --- /dev/null +++ b/loader/core/src/main/java/android/content/res/CompatibilityInfo.java @@ -0,0 +1,18 @@ +package android.content.res; + +import android.os.Parcel; +import android.os.Parcelable; + +public class CompatibilityInfo implements Parcelable { + public static final Parcelable.Creator CREATOR = null; + + @Override + public int describeContents() { + throw new UnsupportedOperationException("STUB"); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + throw new UnsupportedOperationException("STUB"); + } +} \ No newline at end of file diff --git a/loader/core/src/main/java/com/wind/xposed/entry/MMPApplication.java b/loader/core/src/main/java/com/wind/xposed/entry/MMPApplication.java new file mode 100644 index 0000000..2c3969e --- /dev/null +++ b/loader/core/src/main/java/com/wind/xposed/entry/MMPApplication.java @@ -0,0 +1,275 @@ +package com.wind.xposed.entry; + +import static com.wind.xposed.entry.MMPLoader.initAndLoadModules; + +import android.app.Application; +import android.content.Context; + +import com.wind.xposed.entry.util.FileUtils; +import com.wind.xposed.entry.util.ReflectionApiCheck; +import com.wind.xposed.entry.util.XLog; +import com.wind.xposed.entry.util.XpatchUtils; + +import org.lsposed.lspd.yahfa.hooker.YahfaHooker; + +import java.util.ArrayList; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.XposedInit; + +/** + * Created by Windysha + */ +public class MMPApplication extends Application { + private static final String ORIGINAL_APPLICATION_NAME_ASSET_PATH = "original_application_name.ini"; + private static final String TAG = "XpatchProxyApplication"; + private static String originalApplicationName = null; + private static Application sOriginalApplication = null; + private static ClassLoader appClassLoader; + private static Object activityThread; + + final static public int FIRST_ISOLATED_UID = 99000; + final static public int LAST_ISOLATED_UID = 99999; + final static public int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; + final static public int LAST_APP_ZYGOTE_ISOLATED_UID = 98999; + final static public int SHARED_RELRO_UID = 1037; + final static public int PER_USER_RANGE = 100000; + + static public boolean isIsolated() { + int uid = android.os.Process.myUid(); + uid = uid % PER_USER_RANGE; + return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID) || (uid >= FIRST_APP_ZYGOTE_ISOLATED_UID && uid <= LAST_APP_ZYGOTE_ISOLATED_UID); + } + + static { + ReflectionApiCheck.unseal(); + + System.loadLibrary("lspd"); + YahfaHooker.init(); + XposedInit.startsSystemServer = false; + + Context context = XpatchUtils.createAppContext(); + originalApplicationName = FileUtils.readTextFromAssets(context, ORIGINAL_APPLICATION_NAME_ASSET_PATH); + XLog.d(TAG, "original application name " + originalApplicationName); + + if (isIsolated()) { + XLog.d(TAG, "skip isolated process"); + } + else { + if (isApplicationProxied()) { + doHook(); + initAndLoadModules(context); + } + else { + XLog.e(TAG, "something wrong"); + } + } + } + + public MMPApplication() { + super(); + + if (isApplicationProxied()) { + createOriginalApplication(); + } + } + + private static boolean isApplicationProxied() { + if (originalApplicationName != null && !originalApplicationName.isEmpty() && !("android.app.Application").equals(originalApplicationName)) { + return true; + } + else { + return false; + } + } + + private static ClassLoader getAppClassLoader() { + if (appClassLoader != null) { + return appClassLoader; + } + try { + Object mBoundApplication = XposedHelpers.getObjectField(getActivityThread(), "mBoundApplication"); + Object loadedApkObj = XposedHelpers.getObjectField(mBoundApplication, "info"); + appClassLoader = (ClassLoader) XposedHelpers.callMethod(loadedApkObj, "getClassLoader"); + } + catch (Exception e) { + e.printStackTrace(); + } + return appClassLoader; + } + + private static void doHook() { + hookContextImplSetOuterContext(); + hookInstallContentProviders(); + hookActivityAttach(); + hookServiceAttach(); + } + + private static void hookContextImplSetOuterContext() { + XposedHelpers.findAndHookMethod("android.app.ContextImpl", getAppClassLoader(), "setOuterContext", Context.class, new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + replaceApplicationParam(param.args); + // XposedHelpers.setObjectField(param.thisObject, "mOuterContext", sOriginalApplication); + } + }); + } + + private static void hookInstallContentProviders() { + XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.ActivityThread", getAppClassLoader()), "installContentProviders", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + replaceApplicationParam(param.args); + } + }); + } + + private static void hookActivityAttach() { + XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.Activity", getAppClassLoader()), "attach", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + replaceApplicationParam(param.args); + } + }); + } + + private static void hookServiceAttach() { + XposedBridge.hookAllMethods(XposedHelpers.findClass("android.app.Service", getAppClassLoader()), "attach", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + replaceApplicationParam(param.args); + } + }); + } + + private static void replaceApplicationParam(Object[] args) { + if (args == null || args.length == 0) { + return; + } + for (Object para : args) { + if (para instanceof MMPApplication) { + para = sOriginalApplication; + } + } + } + + private static Object getActivityThread() { + if (activityThread == null) { + try { + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + activityThread = XposedHelpers.callStaticMethod(activityThreadClass, "currentActivityThread"); + } + catch (Exception e) { + e.printStackTrace(); + } + } + return activityThread; + } + + @Override + protected void attachBaseContext(Context base) { + + // 将applicationInfo中保存的applcation class name还原为真实的application class name + if (isApplicationProxied()) { + modifyApplicationInfoClassName(); + } + + super.attachBaseContext(base); + + if (isApplicationProxied()) { + attachOrignalBaseContext(base); + setLoadedApkField(base); + } + + // setApplicationLoadedApk(base); + } + + private void attachOrignalBaseContext(Context base) { + try { + XposedHelpers.callMethod(sOriginalApplication, "attachBaseContext", base); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private void setLoadedApkField(Context base) { + // mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; + try { + Class contextImplClass = Class.forName("android.app.ContextImpl"); + Object contextImpl = XposedHelpers.callStaticMethod(contextImplClass, "getImpl", base); + Object loadedApk = XposedHelpers.getObjectField(contextImpl, "mPackageInfo"); + XposedHelpers.setObjectField(sOriginalApplication, "mLoadedApk", loadedApk); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onCreate() { + // setLoadedApkField(sOriginalApplication); + // XposedHelpers.setObjectField(sOriginalApplication, "mLoadedApk", XposedHelpers.getObjectField(this, "mLoadedApk")); + super.onCreate(); + + if (isApplicationProxied()) { + // replaceApplication(); + replaceLoadedApkApplication(); + replaceActivityThreadApplication(); + + sOriginalApplication.onCreate(); + } + } + + private void replaceLoadedApkApplication() { + try { + // replace LoadedApk.java makeApplication() mActivityThread.mAllApplications.add(app); + ArrayList list = (ArrayList) XposedHelpers.getObjectField(getActivityThread(), "mAllApplications"); + list.add(sOriginalApplication); + + Object mBoundApplication = XposedHelpers.getObjectField(getActivityThread(), "mBoundApplication"); // AppBindData + Object loadedApkObj = XposedHelpers.getObjectField(mBoundApplication, "info"); // info + + // replace LoadedApk.java makeApplication() mApplication = app; + XposedHelpers.setObjectField(loadedApkObj, "mApplication", sOriginalApplication); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private void replaceActivityThreadApplication() { + try { + XposedHelpers.setObjectField(getActivityThread(), "mInitialApplication", sOriginalApplication); + } + catch (Exception e) { + e.printStackTrace(); + } + } + + private Application createOriginalApplication() { + if (sOriginalApplication == null) { + try { + sOriginalApplication = (Application) getAppClassLoader().loadClass(originalApplicationName).newInstance(); + } + catch (InstantiationException | ClassNotFoundException | IllegalAccessException e) { + e.printStackTrace(); + } + } + return sOriginalApplication; + } + + private void modifyApplicationInfoClassName() { + try { + Object mBoundApplication = XposedHelpers.getObjectField(getActivityThread(), "mBoundApplication"); // AppBindData + Object applicationInfoObj = XposedHelpers.getObjectField(mBoundApplication, "appInfo"); // info + + XposedHelpers.setObjectField(applicationInfoObj, "className", originalApplicationName); + } + catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/loader/core/src/main/java/com/wind/xposed/entry/MMPLoader.java b/loader/core/src/main/java/com/wind/xposed/entry/MMPLoader.java new file mode 100644 index 0000000..4e52df2 --- /dev/null +++ b/loader/core/src/main/java/com/wind/xposed/entry/MMPLoader.java @@ -0,0 +1,335 @@ +package com.wind.xposed.entry; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Environment; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; + +import com.wind.xposed.entry.util.FileUtils; +import com.wind.xposed.entry.util.XLog; +import com.wind.xposed.entry.util.XpatchUtils; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import dalvik.system.DelegateLastClassLoader; +import de.robv.android.xposed.IXposedHookInitPackageResources; +import de.robv.android.xposed.IXposedHookLoadPackage; +import de.robv.android.xposed.IXposedHookZygoteInit; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelper; +import de.robv.android.xposed.callbacks.XC_LoadPackage; + +public class MMPLoader { + + private static final String TAG = MMPLoader.class.getSimpleName(); + private static final String DIR_BASE = Environment.getExternalStorageDirectory().getAbsolutePath(); + private static final String XPOSED_MODULE_FILE_PATH = "xpmodules.list"; + private static AtomicBoolean hasInited = new AtomicBoolean(false); + private static Context appContext; + + + @SuppressLint("DiscouragedPrivateApi") + public static boolean loadModule(final String moduleApkPath, String moduleOdexDir, String moduleLibPath, final ApplicationInfo currentApplicationInfo, ClassLoader appClassLoader) { + + XLog.i(TAG, "Loading modules from " + moduleApkPath); + + if (!new File(moduleApkPath).exists()) { + XLog.e(TAG, moduleApkPath + " does not exist"); + return false; + } + + ClassLoader mcl = new DelegateLastClassLoader(moduleApkPath, null, appClassLoader); + + try { + if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != appClassLoader) { + Log.e(TAG, " Cannot load module:"); + Log.e(TAG, " The Xposed API classes are compiled into the module's APK."); + Log.e(TAG, " This may cause strange issues and must be fixed by the module developer."); + Log.e(TAG, " For details, see: http://api.xposed.info/using.html"); + return false; + } + } + catch (ClassNotFoundException ignored) { + } + + try (InputStream is = mcl.getResourceAsStream("assets/xposed_init")) { + if (is == null) { + XLog.e(TAG, "assets/xposed_init not found in the APK"); + return false; + } + + BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is)); + String moduleClassName; + while ((moduleClassName = moduleClassesReader.readLine()) != null) { + moduleClassName = moduleClassName.trim(); + if (moduleClassName.isEmpty() || moduleClassName.startsWith("#")) { + continue; + } + + try { + XLog.i(TAG, "Loading class " + moduleClassName); + Class moduleClass = mcl.loadClass(moduleClassName); + + if (!XposedHelper.isIXposedMod(moduleClass)) { + Log.w(TAG, "This class doesn't implement any sub-interface of IXposedMod, skipping it"); + continue; + } + else if (IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) { + Log.w(TAG, "This class requires resource-related hooks (which are disabled), skipping it."); + continue; + } + + final Object moduleInstance = moduleClass.newInstance(); + if (moduleInstance instanceof IXposedHookZygoteInit) { + XposedHelper.callInitZygote(moduleApkPath, moduleInstance); + } + + if (moduleInstance instanceof IXposedHookLoadPackage) { + // hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance)); + IXposedHookLoadPackage.Wrapper wrapper = new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance, moduleApkPath); + XposedBridge.CopyOnWriteSortedSet xc_loadPackageCopyOnWriteSortedSet = new XposedBridge.CopyOnWriteSortedSet<>(); + xc_loadPackageCopyOnWriteSortedSet.add(wrapper); + XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(xc_loadPackageCopyOnWriteSortedSet); + lpparam.packageName = currentApplicationInfo.packageName; + lpparam.processName = (String) Class.forName("android.app.ActivityThread").getDeclaredMethod("currentProcessName").invoke(null); + lpparam.classLoader = appClassLoader; + lpparam.appInfo = currentApplicationInfo; + lpparam.isFirstApplication = true; + XC_LoadPackage.callAll(lpparam); + } + + if (moduleInstance instanceof IXposedHookInitPackageResources) { + XLog.w(TAG, "unsupport resource hook"); + } + } + catch (Throwable t) { + XLog.e(TAG, "", t); + } + } + } + catch (IOException e) { + XLog.e(TAG, "", e); + } + return true; + } + + public static void initAndLoadModules() { + Context context = XpatchUtils.createAppContext(); + initAndLoadModules(context); + } + + public static void initAndLoadModules(Context context) { + if (!hasInited.compareAndSet(false, true)) { + XLog.w(TAG, "has been init"); + return; + } + + if (context == null) { + XLog.e(TAG, "try to init with context null"); + return; + } + + appContext = context; + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { + if (!FileUtils.isSdcardPermissionGranted(context)) { + XLog.e(TAG, "file permission is not granted, can not control xposed module by file " + XPOSED_MODULE_FILE_PATH); + } + } + + initSELinux(context); + + ClassLoader originClassLoader = context.getClassLoader(); + List modulePathList = loadAllInstalledModule(context); + + for (String modulePath : modulePathList) { + String dexPath = context.getDir("xposed_plugin_dex", Context.MODE_PRIVATE).getAbsolutePath(); + if (!TextUtils.isEmpty(modulePath)) { + MMPLoader.loadModule(modulePath, dexPath, null, context.getApplicationInfo(), originClassLoader); + } + } + } + + private static void initSELinux(Context context) { + XposedHelper.initSeLinux(context.getApplicationInfo().processName); + } + + private static List loadAllInstalledModule(Context context) { + PackageManager pm = context.getPackageManager(); + List modulePathList = new ArrayList<>(); + + List packageNameList = loadPackageNameListFromFile(true); + List> installedModuleList = new ArrayList<>(); + + boolean configFileExist = configFileExist(); + + for (PackageInfo pkg : pm.getInstalledPackages(PackageManager.GET_META_DATA)) { + ApplicationInfo app = pkg.applicationInfo; + if (!app.enabled) { + continue; + } + if (app.metaData != null && (app.metaData.containsKey("xposedmodule"))) { + String apkPath = pkg.applicationInfo.publicSourceDir; + String apkName = context.getPackageManager().getApplicationLabel(pkg.applicationInfo).toString(); + if (TextUtils.isEmpty(apkPath)) { + apkPath = pkg.applicationInfo.sourceDir; + } + if (!TextUtils.isEmpty(apkPath) && (!configFileExist || packageNameList == null || packageNameList.contains(app.packageName))) { + XLog.d(TAG, "query installed module path " + apkPath); + modulePathList.add(apkPath); + } + installedModuleList.add(Pair.create(pkg.applicationInfo.packageName, apkName)); + } + } + + final List> installedModuleListFinal = installedModuleList; + + new Thread(new Runnable() { + @Override + public void run() { + List savedPackageNameList = loadPackageNameListFromFile(false); + if (savedPackageNameList == null) { + savedPackageNameList = new ArrayList<>(); + } + List> addPackageList = new ArrayList<>(); + for (Pair packgagePair : installedModuleListFinal) { + if (!savedPackageNameList.contains(packgagePair.first)) { + XLog.d(TAG, "append " + packgagePair + " to " + XPOSED_MODULE_FILE_PATH); + addPackageList.add(packgagePair); + } + } + try { + appendPackageNameToFile(addPackageList); + } + catch (IOException e) { + e.printStackTrace(); + } + } + }).start(); + return modulePathList; + } + + // 从sd卡中加载指定文件,以加载指定的xposed module + private static List loadPackageNameListFromFile(boolean loadActivedPackages) { + File moduleFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH); + if (!moduleFile.exists()) { + return null; + } + List modulePackageList = new ArrayList<>(); + + FileInputStream fileInputStream = null; + BufferedReader bufferedReader = null; + try { + fileInputStream = new FileInputStream(moduleFile); + bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream)); + String modulePackageName; + while ((modulePackageName = bufferedReader.readLine()) != null) { + modulePackageName = modulePackageName.trim(); + if (modulePackageName.isEmpty() || (modulePackageName.startsWith("#") && loadActivedPackages)) { + continue; + } + + if (modulePackageName.startsWith("#")) { + modulePackageName = modulePackageName.substring(1); + } + int index = modulePackageName.indexOf("#"); + if (index > 0) { + modulePackageName = modulePackageName.substring(0, index); + } + XLog.d(TAG, "load " + XPOSED_MODULE_FILE_PATH + " file result, modulePackageName " + modulePackageName); + modulePackageList.add(modulePackageName); + } + } + catch (IOException e) { + e.printStackTrace(); + return null; + } + finally { + closeStream(fileInputStream); + closeStream(bufferedReader); + } + return modulePackageList; + } + + private static void appendPackageNameToFile(List> packageNameList) throws IOException { + + if (isEmpty(packageNameList)) { + return; + } + + File moduleFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH); + if (!moduleFile.exists()) { + if (!moduleFile.createNewFile()) { + throw new IllegalStateException("create " + XPOSED_MODULE_FILE_PATH + " err"); + } + } + FileOutputStream outputStream = null; + BufferedWriter writer = null; + try { + outputStream = new FileOutputStream(moduleFile, true); + writer = new BufferedWriter(new OutputStreamWriter(outputStream)); + + for (Pair packageInfo : packageNameList) { + String packageName = packageInfo.first; + String appName = packageInfo.second; + writer.write(packageName + "#" + appName); + writer.write("\n"); + XLog.d(TAG, "append new pkg to " + XPOSED_MODULE_FILE_PATH); + } + writer.flush(); + } + catch (Exception e) { + e.printStackTrace(); + } + finally { + closeStream(outputStream); + closeStream(writer); + } + } + + private static boolean configFileExist() { + File moduleConfigFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH); + return moduleConfigFile.exists(); + } + + private static void closeStream(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } + catch (IOException e) { + e.printStackTrace(); + } + } + } + + private static boolean isEmpty(Collection collection) { + if (collection == null || collection.size() == 0) { + return true; + } + return false; + } + + public static Context getAppContext() { + return appContext; + } +} diff --git a/loader/core/src/main/java/com/wind/xposed/entry/util/FileUtils.java b/loader/core/src/main/java/com/wind/xposed/entry/util/FileUtils.java new file mode 100644 index 0000000..0b23bfe --- /dev/null +++ b/loader/core/src/main/java/com/wind/xposed/entry/util/FileUtils.java @@ -0,0 +1,55 @@ +package com.wind.xposed.entry.util; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Process; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +public class FileUtils { + + //读写权限 + private static String[] PERMISSIONS_STORAGE = { + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + }; + + public static boolean isSdcardPermissionGranted(Context context) { + int pid = android.os.Process.myPid(); + int uid = Process.myUid(); + return context.checkPermission(PERMISSIONS_STORAGE[0], pid, uid) == PackageManager.PERMISSION_GRANTED && context.checkPermission(PERMISSIONS_STORAGE[1], pid, uid) == PackageManager.PERMISSION_GRANTED; + } + + public static String readTextFromAssets(Context context, String assetsFileName) { + if (context == null) { + throw new IllegalStateException("context null"); + } + try { + InputStream is = context.getAssets().open(assetsFileName); + return readTextFromInputStream(is); + } + catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public static String readTextFromInputStream(InputStream is) { + try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader(reader)) { + StringBuilder builder = new StringBuilder(); + String str; + while ((str = bufferedReader.readLine()) != null) { + builder.append(str); + } + return builder.toString(); + } + catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/loader/core/src/main/java/com/wind/xposed/entry/util/ReflectionApiCheck.java b/loader/core/src/main/java/com/wind/xposed/entry/util/ReflectionApiCheck.java new file mode 100644 index 0000000..7dc9d63 --- /dev/null +++ b/loader/core/src/main/java/com/wind/xposed/entry/util/ReflectionApiCheck.java @@ -0,0 +1,93 @@ +package com.wind.xposed.entry.util; + +import static android.os.Build.VERSION.SDK_INT; + +import android.os.Build; +import android.util.Log; + +import java.lang.reflect.Method; + +/** + * @author Windysha + */ +public class ReflectionApiCheck { + + private static final String TAG = ReflectionApiCheck.class.getSimpleName(); + private static final int ERROR_EXEMPT_FAILED = -21; + private static Object sVmRuntime; + private static Method setHiddenApiExemptions; + + static { + if (SDK_INT >= Build.VERSION_CODES.P) { + try { + Method forName = Class.class.getDeclaredMethod("forName", String.class); + Method getDeclaredMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class); + + Class vmRuntimeClass = (Class) forName.invoke(null, "dalvik.system.VMRuntime"); + Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "getRuntime", null); + if (getRuntime == null) { + throw new IllegalStateException("getRuntime method null"); + } + setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class}); + sVmRuntime = getRuntime.invoke(null); + } + catch (Throwable e) { + Log.e(TAG, "reflect bootstrap failed:", e); + } + } + } + + public static int unseal() { + if (SDK_INT < 28) { + // Below Android P, ignore + return 0; + } + + // try exempt API first. + if (exemptAll()) { + return 0; + } + else { + return ERROR_EXEMPT_FAILED; + } + } + + /** + * make the method exempted from hidden API check. + * + * @param method the method signature prefix. + * @return true if success. + */ + public static boolean exempt(String method) { + return exempt(new String[]{method}); + } + + /** + * make specific methods exempted from hidden API check. + * + * @param methods the method signature prefix, such as "Ldalvik/system", "Landroid" or even "L" + * @return true if success + */ + public static boolean exempt(String... methods) { + if (sVmRuntime == null || setHiddenApiExemptions == null) { + return false; + } + + try { + setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{methods}); + return true; + } + catch (Throwable e) { + return false; + } + } + + /** + * Make all hidden API exempted. + * + * @return true if success. + */ + public static boolean exemptAll() { + return exempt(new String[]{"L"}); + } +} diff --git a/loader/core/src/main/java/com/wind/xposed/entry/util/XLog.java b/loader/core/src/main/java/com/wind/xposed/entry/util/XLog.java new file mode 100644 index 0000000..d5d4d4b --- /dev/null +++ b/loader/core/src/main/java/com/wind/xposed/entry/util/XLog.java @@ -0,0 +1,44 @@ +package com.wind.xposed.entry.util; + +import com.wind.xposed.entry.BuildConfig; + +public class XLog { + + private static boolean enableLog = BuildConfig.DEBUG; + + public static void d(String tag, String msg) { + if (enableLog) { + android.util.Log.d(tag, msg); + } + } + + public static void v(String tag, String msg) { + if (enableLog) { + android.util.Log.v(tag, msg); + } + } + + public static void w(String tag, String msg) { + if (enableLog) { + android.util.Log.w(tag, msg); + } + } + + public static void i(String tag, String msg) { + if (enableLog) { + android.util.Log.i(tag, msg); + } + } + + public static void e(String tag, String msg) { + if (enableLog) { + android.util.Log.e(tag, msg); + } + } + + public static void e(String tag, String msg, Throwable tr) { + if (enableLog) { + android.util.Log.e(tag, msg, tr); + } + } +} diff --git a/loader/core/src/main/java/com/wind/xposed/entry/util/XpatchUtils.java b/loader/core/src/main/java/com/wind/xposed/entry/util/XpatchUtils.java new file mode 100644 index 0000000..cc8683f --- /dev/null +++ b/loader/core/src/main/java/com/wind/xposed/entry/util/XpatchUtils.java @@ -0,0 +1,62 @@ +package com.wind.xposed.entry.util; + +import android.app.ActivityThread; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.util.Log; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class XpatchUtils { + final static String TAG = "MMP" + XpatchUtils.class.getSimpleName(); + + public static Context createAppContext() { + try { + Class activityThreadClass = Class.forName("android.app.ActivityThread"); + Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); + currentActivityThreadMethod.setAccessible(true); + + Object activityThreadObj = currentActivityThreadMethod.invoke(null); + + Field boundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication"); + boundApplicationField.setAccessible(true); + Object mBoundApplication = boundApplicationField.get(activityThreadObj); // AppBindData + if (mBoundApplication == null) { + Log.e(TAG, "mBoundApplication null"); + return null; + } + Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info + infoField.setAccessible(true); + Object loadedApkObj = infoField.get(mBoundApplication); // LoadedApk + if (loadedApkObj == null) { + Log.e(TAG, "loadedApkObj null"); + return null; + } + Class contextImplClass = Class.forName("android.app.ContextImpl"); + Method createAppContextMethod = contextImplClass.getDeclaredMethod("createAppContext", activityThreadClass, loadedApkObj.getClass()); + createAppContextMethod.setAccessible(true); + + Object context = createAppContextMethod.invoke(null, (ActivityThread) activityThreadObj, loadedApkObj); + + if (context instanceof Context) { + return (Context) context; + } + } + catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { + e.printStackTrace(); + } + return null; + } + + public static boolean isApkDebugable(Context context) { + try { + ApplicationInfo info = context.getApplicationInfo(); + return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + } + catch (Exception ignore) { + } + return false; + } +} diff --git a/loader/core/src/main/java/de/robv/android/xposed/XposedHelper.java b/loader/core/src/main/java/de/robv/android/xposed/XposedHelper.java new file mode 100644 index 0000000..3ba39ad --- /dev/null +++ b/loader/core/src/main/java/de/robv/android/xposed/XposedHelper.java @@ -0,0 +1,33 @@ +package de.robv.android.xposed; + +import de.robv.android.xposed.IXposedHookZygoteInit; +import de.robv.android.xposed.IXposedMod; +import de.robv.android.xposed.XC_MethodHook; + +import java.lang.reflect.Member; + +public class XposedHelper { + + private static final String TAG = "XposedHelper"; + + public static void initSeLinux(String processName) { + // SELinuxHelper.initOnce(); + // SELinuxHelper.initForProcess(processName); + } + + public static boolean isIXposedMod(Class moduleClass) { + return IXposedMod.class.isAssignableFrom(moduleClass); + } + + + public static XC_MethodHook.Unhook newUnHook(XC_MethodHook XC_MethodHook, Member member) { + return XC_MethodHook.new Unhook(member); + } + + public static void callInitZygote(String modulePath, Object moduleInstance) throws Throwable { + IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam(); + param.modulePath = modulePath; + param.startsSystemServer = false; + ((IXposedHookZygoteInit) moduleInstance).initZygote(param); + } +} diff --git a/loader/core/src/main/res/values/strings.xml b/loader/core/src/main/res/values/strings.xml new file mode 100644 index 0000000..49fc91e --- /dev/null +++ b/loader/core/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + library + diff --git a/loader/gradle.properties b/loader/gradle.properties new file mode 100644 index 0000000..6a7638b --- /dev/null +++ b/loader/gradle.properties @@ -0,0 +1,20 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +version_name=1.0 +version_code=1 + +android.useAndroidX=true + + diff --git a/loader/gradle/wrapper/gradle-wrapper.jar b/loader/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/loader/gradle/wrapper/gradle-wrapper.jar differ diff --git a/loader/gradle/wrapper/gradle-wrapper.properties b/loader/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ec7cb52 --- /dev/null +++ b/loader/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Mar 25 19:56:14 CST 2021 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/loader/gradlew b/loader/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/loader/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/loader/gradlew.bat b/loader/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/loader/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/loader/settings.gradle b/loader/settings.gradle new file mode 100644 index 0000000..ec28381 --- /dev/null +++ b/loader/settings.gradle @@ -0,0 +1,14 @@ +include ':app' +rootProject.name='MMPLoader' +include ':xpatchcore' +project(':xpatchcore').projectDir = new File('core') +include ':lspcore' +project(':lspcore').projectDir = new File('mmp/core') +include ':hiddenapi-stubs' +project(':hiddenapi-stubs').projectDir = new File('mmp/hiddenapi-stubs') +include ':interface' +project(':interface').projectDir = new File('mmp/service/interface') +include ':hiddenapi-bridge' +project(':hiddenapi-bridge').projectDir = new File('mmp/hiddenapi-bridge') +include ':manager-service' +project(':manager-service').projectDir = new File('mmp/manager-service')