├── app ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── aidl │ │ └── top │ │ │ └── canyie │ │ │ └── dreamland │ │ │ └── ipc │ │ │ ├── ModuleInfo.aidl │ │ │ └── IDreamlandManager.aidl │ │ ├── cpp │ │ ├── dreamland │ │ │ ├── native_hook.h │ │ │ ├── android.cpp │ │ │ ├── android.h │ │ │ ├── dex_loader.h │ │ │ ├── flavor.h │ │ │ ├── resources_hook.h │ │ │ ├── binder.h │ │ │ ├── zygisk_flavor.cpp │ │ │ ├── flavor.cpp │ │ │ ├── dex_loader.cpp │ │ │ ├── dreamland.h │ │ │ ├── binder.cpp │ │ │ ├── native_hook.cpp │ │ │ ├── riru_flavor.cpp │ │ │ └── resources_hook.cpp │ │ ├── utils │ │ │ ├── utils.h │ │ │ ├── byte_order.h │ │ │ ├── scoped_elf.h │ │ │ ├── well_known_classes.h │ │ │ ├── log.h │ │ │ ├── macros.h │ │ │ ├── scoped_local_ref.h │ │ │ ├── well_known_classes.cpp │ │ │ └── jni_helper.h │ │ ├── pine.h │ │ ├── CMakeLists.txt │ │ └── riru.h │ │ ├── java │ │ ├── top │ │ │ └── canyie │ │ │ │ └── dreamland │ │ │ │ ├── utils │ │ │ │ ├── AppConstants.java │ │ │ │ ├── reflect │ │ │ │ │ ├── ReflectiveException.java │ │ │ │ │ ├── UncheckedNoSuchFieldException.java │ │ │ │ │ ├── UncheckedNoSuchMethodException.java │ │ │ │ │ ├── UncheckedClassNotFoundException.java │ │ │ │ │ ├── UncheckedIllegalAccessException.java │ │ │ │ │ ├── UncheckedInstantiationException.java │ │ │ │ │ └── UncheckedInvocationTargetException.java │ │ │ │ ├── AppGlobals.java │ │ │ │ ├── Zygote.java │ │ │ │ ├── RuntimeUtils.java │ │ │ │ ├── BuildUtils.java │ │ │ │ ├── Preconditions.java │ │ │ │ ├── collections │ │ │ │ │ └── ConcurrentHashSet.java │ │ │ │ ├── IOUtils.java │ │ │ │ ├── HiddenApis.java │ │ │ │ └── DLog.java │ │ │ │ ├── DreamlandBridge.java │ │ │ │ ├── core │ │ │ │ ├── GsonBasedManager.java │ │ │ │ ├── AppManager.java │ │ │ │ ├── BaseManager.java │ │ │ │ ├── ModuleManager.java │ │ │ │ └── PackageMonitor.java │ │ │ │ ├── ipc │ │ │ │ ├── ModuleInfo.java │ │ │ │ └── BinderServiceProxy.java │ │ │ │ └── GetClassLoaderHook.java │ │ ├── mirror │ │ │ └── android │ │ │ │ ├── os │ │ │ │ └── ServiceManager.java │ │ │ │ └── app │ │ │ │ └── ActivityThread.java │ │ ├── android │ │ │ └── content │ │ │ │ └── res │ │ │ │ ├── XResForwarder.java │ │ │ │ └── XModuleResources.java │ │ └── de │ │ │ └── robv │ │ │ └── android │ │ │ └── xposed │ │ │ ├── IXposedHookInitPackageResources.java │ │ │ ├── callbacks │ │ │ ├── XC_InitPackageResources.java │ │ │ └── XC_LayoutInflated.java │ │ │ ├── services │ │ │ ├── FileResult.java │ │ │ └── DirectAccessService.java │ │ │ └── SELinuxHelper.java │ │ └── AndroidManifest.xml └── proguard-rules.pro ├── template ├── META-INF │ └── com │ │ └── google │ │ └── android │ │ ├── updater-script │ │ └── update-binary ├── system.prop ├── sepolicy.rule ├── uninstall.sh ├── post-fs-data.sh ├── service.sh ├── languages.sh └── customize.sh ├── settings.gradle ├── external ├── pine │ ├── arm64-v8a │ │ └── libpine.a │ └── armeabi-v7a │ │ └── libpine.a └── pine-enhances │ ├── arm64-v8a │ └── libpine-enhances.a │ └── armeabi-v7a │ └── libpine-enhances.a ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── hiddenapi-stubs ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ ├── android │ │ ├── content │ │ │ ├── res │ │ │ │ ├── ResourcesKey.java │ │ │ │ ├── ResourcesImpl.java │ │ │ │ ├── CompatibilityInfo.java │ │ │ │ ├── AssetManagerHidden.java │ │ │ │ ├── TypedArrayHidden.java │ │ │ │ └── ResourcesHidden.java │ │ │ └── pm │ │ │ │ ├── PackageParser.java │ │ │ │ └── IPackageManager.java │ │ ├── os │ │ │ ├── SystemProperties.java │ │ │ ├── UserHandleHidden.java │ │ │ └── SELinux.java │ │ └── app │ │ │ ├── LoadedApk.java │ │ │ └── ActivityThread.java │ │ └── xposed │ │ └── dummy │ │ ├── XTypedArraySuperClass.java │ │ └── XResourcesSuperClass.java └── build.gradle ├── gradle.properties ├── .gitignore ├── azure-pipelines.yml ├── README_CN.md ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /template/META-INF/com/google/android/updater-script: -------------------------------------------------------------------------------- 1 | #MAGISK 2 | -------------------------------------------------------------------------------- /template/system.prop: -------------------------------------------------------------------------------- 1 | dalvik.vm.dex2oat-flags=--inline-max-code-units=0 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':hiddenapi-stubs' 2 | rootProject.name='Dreamland' 3 | -------------------------------------------------------------------------------- /external/pine/arm64-v8a/libpine.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/external/pine/arm64-v8a/libpine.a -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Dreamland 3 | 4 | -------------------------------------------------------------------------------- /external/pine/armeabi-v7a/libpine.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/external/pine/armeabi-v7a/libpine.a -------------------------------------------------------------------------------- /template/sepolicy.rule: -------------------------------------------------------------------------------- 1 | allow system_server system_server process execmem 2 | allow system_server apk_data_file file execute 3 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/aidl/top/canyie/dreamland/ipc/ModuleInfo.aidl: -------------------------------------------------------------------------------- 1 | // ModuleInfo.aidl 2 | package top.canyie.dreamland.ipc; 3 | 4 | parcelable ModuleInfo; 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /external/pine-enhances/arm64-v8a/libpine-enhances.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/external/pine-enhances/arm64-v8a/libpine-enhances.a -------------------------------------------------------------------------------- /external/pine-enhances/armeabi-v7a/libpine-enhances.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canyie/Dreamland/HEAD/external/pine-enhances/armeabi-v7a/libpine-enhances.a -------------------------------------------------------------------------------- /template/uninstall.sh: -------------------------------------------------------------------------------- 1 | rm -rf /data/misc/dreamland 2 | 3 | # Before v22 4 | rm -rf /data/misc/riru/modules/dreamland 5 | 6 | # After v22 7 | rm -rf /data/adb/riru/modules/dreamland 8 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/content/res/ResourcesKey.java: -------------------------------------------------------------------------------- 1 | package android.content.res; 2 | 3 | /** 4 | * @author canyie 5 | */ 6 | public final class ResourcesKey { 7 | public /*final*/ String mResDir; 8 | } 9 | -------------------------------------------------------------------------------- /template/post-fs-data.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | 3 | MODDIR=${0%/*} 4 | 5 | [ -f "${MODDIR}/sepolicy.rule" ] && exit 0 6 | 7 | magiskpolicy --live "allow system_server system_server process execmem" \ 8 | "allow system_server apk_data_file file execute" 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 11 15:07:13 CST 2019 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-8.4-bin.zip 7 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/content/res/ResourcesImpl.java: -------------------------------------------------------------------------------- 1 | package android.content.res; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | 6 | /** 7 | * @author canyie 8 | */ 9 | @TargetApi(Build.VERSION_CODES.N) public class ResourcesImpl { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/native_hook.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2024/2/5. 3 | // 4 | 5 | #ifndef DREAMLAND_NATIVE_HOOK_H 6 | #define DREAMLAND_NATIVE_HOOK_H 7 | 8 | #include 9 | 10 | namespace dreamland { 11 | class NativeHook { 12 | public: 13 | static void RegisterNatives(JNIEnv* env, jclass main); 14 | }; 15 | } // dreamland 16 | 17 | #endif //DREAMLAND_NATIVE_HOOK_H 18 | -------------------------------------------------------------------------------- /app/src/main/cpp/utils/utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2019/12/1. 3 | // 4 | 5 | #ifndef DREAMLAND_UTILS_H 6 | #define DREAMLAND_UTILS_H 7 | 8 | #ifdef __arm__ 9 | #define SYSTEM_LIB_BASE_PATH "/system/lib/" 10 | #elif defined(__aarch64__) 11 | #define SYSTEM_LIB_BASE_PATH "/system/lib64/" 12 | #else 13 | #error "Unknown ABI, not suppport now" 14 | #endif 15 | 16 | 17 | #endif //DREAMLAND_UTILS_H 18 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/AppConstants.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils; 2 | 3 | /** 4 | * @author canyie 5 | */ 6 | public final class AppConstants { 7 | public static final String ANDROID = "android"; 8 | public static final String[] ARRAY_ANDROID = new String[] {ANDROID}; 9 | public static final String[] EMPTY_STRING_ARRAY = new String[0]; 10 | private AppConstants() {} 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/cpp/utils/byte_order.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2020/8/9. 3 | // 4 | 5 | #ifndef DREAMLAND_BYTE_ORDER_H 6 | #define DREAMLAND_BYTE_ORDER_H 7 | 8 | /** 9 | * Android only use little-endian. 10 | */ 11 | 12 | #define dtohl(x) (x) 13 | #define dtohs(x) (x) 14 | #define htodl(x) (x) 15 | #define htods(x) (x) 16 | 17 | #define fromlel(x) (x) 18 | #define tolel(x) (x) 19 | 20 | #endif //DREAMLAND_BYTE_ORDER_H 21 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/xposed/dummy/XTypedArraySuperClass.java: -------------------------------------------------------------------------------- 1 | package xposed.dummy; 2 | 3 | import android.content.res.TypedArrayHidden; 4 | 5 | /** 6 | * This class is used as super class of XResources.XTypedArray. 7 | */ 8 | public class XTypedArraySuperClass extends TypedArrayHidden { 9 | protected XTypedArraySuperClass() { 10 | super(null); 11 | throw new UnsupportedOperationException("Stub!"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/xposed/dummy/XResourcesSuperClass.java: -------------------------------------------------------------------------------- 1 | package xposed.dummy; 2 | 3 | import android.content.res.Resources; 4 | 5 | /** 6 | * This class is used as super class of XResources. 7 | */ 8 | public class XResourcesSuperClass extends Resources { 9 | protected XResourcesSuperClass(ClassLoader classLoader) { 10 | super(null, null, null); 11 | throw new UnsupportedOperationException("Stub!"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/os/SystemProperties.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | /** 4 | * @author canyie 5 | */ 6 | public final class SystemProperties { 7 | private SystemProperties() { 8 | throw new UnsupportedOperationException("Stub!"); 9 | } 10 | 11 | public static String get(String key) { 12 | throw new UnsupportedOperationException("Stub!"); 13 | } 14 | 15 | public static String get(String key, String def) { 16 | throw new UnsupportedOperationException("Stub!"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hiddenapi-stubs/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 Creator CREATOR = null; 18 | } 19 | -------------------------------------------------------------------------------- /hiddenapi-stubs/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 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/reflect/ReflectiveException.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils.reflect; 2 | 3 | /** 4 | * Created by canyie on 2019/10/24. 5 | */ 6 | public class ReflectiveException extends RuntimeException { 7 | public ReflectiveException() { 8 | } 9 | 10 | public ReflectiveException(String message) { 11 | super(message); 12 | } 13 | 14 | public ReflectiveException(Throwable cause) { 15 | super(cause); 16 | } 17 | 18 | public ReflectiveException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/android.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2020/2/4. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "android.h" 11 | #include "../pine.h" 12 | #include "../utils/log.h" 13 | #include "../utils/scoped_local_ref.h" 14 | #include "../utils/jni_helper.h" 15 | 16 | using namespace dreamland; 17 | 18 | int Android::version = 0; 19 | 20 | void Android::Initialize() { 21 | char android_level_str[8]; 22 | __system_property_get("ro.build.version.sdk", android_level_str); 23 | Android::version = atoi(android_level_str); 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/android.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2020/2/4. 3 | // 4 | 5 | #ifndef DREAMLAND_ANDROID_H 6 | #define DREAMLAND_ANDROID_H 7 | 8 | #include 9 | #include "../utils/macros.h" 10 | 11 | namespace dreamland { 12 | class Android { 13 | public: 14 | static int version; 15 | static void Initialize(); 16 | static constexpr int kN = 24; 17 | static constexpr int kO = 26; 18 | static constexpr int kP = 28; 19 | static constexpr int kQ = 29; 20 | 21 | private: 22 | 23 | DISALLOW_IMPLICIT_CONSTRUCTORS(Android); 24 | }; 25 | } 26 | 27 | #endif //DREAMLAND_ANDROID_H 28 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/AppGlobals.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.util.concurrent.ExecutorService; 6 | import java.util.concurrent.Executors; 7 | 8 | /** 9 | * @author canyie 10 | */ 11 | public final class AppGlobals { 12 | private static final Gson GSON = new Gson(); 13 | private static final ExecutorService DEFAULT_EXECUTOR = Executors.newFixedThreadPool(4); 14 | 15 | public static Gson getGson() { 16 | return GSON; 17 | } 18 | 19 | public static ExecutorService getDefaultExecutor() { 20 | return DEFAULT_EXECUTOR; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/reflect/UncheckedNoSuchFieldException.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils.reflect; 2 | 3 | /** 4 | * Created by canyie on 2019/10/24. 5 | */ 6 | public class UncheckedNoSuchFieldException extends ReflectiveException { 7 | public UncheckedNoSuchFieldException() { 8 | } 9 | 10 | public UncheckedNoSuchFieldException(String message) { 11 | super(message); 12 | } 13 | 14 | public UncheckedNoSuchFieldException(Throwable cause) { 15 | super(cause); 16 | } 17 | 18 | public UncheckedNoSuchFieldException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/reflect/UncheckedNoSuchMethodException.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils.reflect; 2 | 3 | /** 4 | * Created by canyie on 2019/10/24. 5 | */ 6 | public class UncheckedNoSuchMethodException extends ReflectiveException { 7 | public UncheckedNoSuchMethodException() { 8 | } 9 | 10 | public UncheckedNoSuchMethodException(String message) { 11 | super(message); 12 | } 13 | 14 | public UncheckedNoSuchMethodException(Throwable cause) { 15 | super(cause); 16 | } 17 | 18 | public UncheckedNoSuchMethodException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/reflect/UncheckedClassNotFoundException.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils.reflect; 2 | 3 | /** 4 | * Created by canyie on 2019/10/24. 5 | */ 6 | public class UncheckedClassNotFoundException extends ReflectiveException { 7 | public UncheckedClassNotFoundException() { 8 | } 9 | 10 | public UncheckedClassNotFoundException(String message) { 11 | super(message); 12 | } 13 | 14 | public UncheckedClassNotFoundException(Throwable cause) { 15 | super(cause); 16 | } 17 | 18 | public UncheckedClassNotFoundException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/reflect/UncheckedIllegalAccessException.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils.reflect; 2 | 3 | /** 4 | * Created by canyie on 2019/10/24. 5 | */ 6 | public class UncheckedIllegalAccessException extends ReflectiveException { 7 | public UncheckedIllegalAccessException() { 8 | } 9 | 10 | public UncheckedIllegalAccessException(String message) { 11 | super(message); 12 | } 13 | 14 | public UncheckedIllegalAccessException(Throwable cause) { 15 | super(cause); 16 | } 17 | 18 | public UncheckedIllegalAccessException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/reflect/UncheckedInstantiationException.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils.reflect; 2 | 3 | /** 4 | * Created by canyie on 2019/10/24. 5 | */ 6 | public final class UncheckedInstantiationException extends ReflectiveException { 7 | public UncheckedInstantiationException() { 8 | } 9 | 10 | public UncheckedInstantiationException(String message) { 11 | super(message); 12 | } 13 | 14 | public UncheckedInstantiationException(Throwable cause) { 15 | super(cause); 16 | } 17 | 18 | public UncheckedInstantiationException(String message, Throwable cause) { 19 | super(message, cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/content/res/AssetManagerHidden.java: -------------------------------------------------------------------------------- 1 | package android.content.res; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | import dev.rikka.tools.refine.RefineAs; 7 | 8 | @RefineAs(AssetManager.class) 9 | public final class AssetManagerHidden { 10 | public AssetManagerHidden() { 11 | throw new UnsupportedOperationException("Stub!"); 12 | } 13 | 14 | public final int addAssetPath(String path) { 15 | throw new UnsupportedOperationException("Stub!"); 16 | } 17 | 18 | public void close() { 19 | throw new UnsupportedOperationException("Stub!"); 20 | } 21 | 22 | public final InputStream open(String fileName) throws IOException { 23 | throw new UnsupportedOperationException("Stub!"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/dex_loader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2021/2/2. 3 | // 4 | 5 | #ifndef DREAMLAND_DEX_LOADER_H 6 | #define DREAMLAND_DEX_LOADER_H 7 | 8 | #include 9 | 10 | namespace dreamland { 11 | class DexLoader { 12 | public: 13 | static void Prepare(JNIEnv* env); 14 | static jobject FromMemory(JNIEnv* env, char* dex, const size_t size); 15 | static jobject FromFile(JNIEnv* env, const char* path); 16 | 17 | private: 18 | static jclass PathClassLoader; // For Nougat only 19 | static jmethodID PathClassLoader_init; 20 | static jclass InMemoryDexClassLoader; // For Oreo+ 21 | static jmethodID InMemoryDexClassLoader_init; 22 | }; 23 | } 24 | 25 | #endif //DREAMLAND_DEX_LOADER_H 26 | -------------------------------------------------------------------------------- /app/src/main/cpp/pine.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2020/7/3. 3 | // 4 | 5 | #ifndef DREAMLAND_PINE_H 6 | #define DREAMLAND_PINE_H 7 | 8 | #include 9 | 10 | extern "C" { 11 | bool register_Pine(JNIEnv* env, jclass Pine); 12 | bool register_Ruler(JNIEnv* env, jclass Ruler); 13 | bool init_PineEnhances(JavaVM* jvm, JNIEnv* env, jclass cls); 14 | void PineSetAndroidVersion(int version); 15 | void* PineOpenElf(const char* elf); 16 | void PineCloseElf(void* handle); 17 | void* PineGetElfSymbolAddress(void* handle, const char* symbol, bool warn_if_missing); 18 | bool PineNativeInlineHookSymbolNoBackup(const char* elf, const char* symbol, void* replace); 19 | void PineNativeInlineHookFuncNoBackup(void* target, void* replace); 20 | }; 21 | 22 | #endif //DREAMLAND_PINE_H 23 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/flavor.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2022/2/28. 3 | // 4 | 5 | #ifndef DREAMLAND_FLAVOR_H 6 | #define DREAMLAND_FLAVOR_H 7 | 8 | #include 9 | 10 | namespace dreamland { 11 | class Flavor { 12 | public: 13 | static void OnModuleLoaded(bool zygote); 14 | 15 | static bool IsDisabled(); 16 | 17 | // Return true if the process should be skipped 18 | static bool ShouldSkip(bool is_child_zygote, int uid); 19 | 20 | static void PreFork(JNIEnv* env, bool zygote); 21 | static void PostForkSystemServer(JNIEnv* env); 22 | 23 | // Return true if we cannot unload our library in the current process 24 | static bool PostForkApp(JNIEnv* env, bool main_zygote); 25 | }; 26 | } 27 | 28 | #endif //DREAMLAND_FLAVOR_H 29 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/os/UserHandleHidden.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | import dev.rikka.tools.refine.RefineAs; 4 | 5 | /** 6 | * @author canyie 7 | */ 8 | @RefineAs(UserHandle.class) 9 | public final class UserHandleHidden { 10 | public static final int USER_ALL = -1; 11 | // In case to prevent constant folding optimization from executing by compiler 12 | public static final UserHandleHidden ALL = new UserHandleHidden(USER_ALL); 13 | 14 | private UserHandleHidden(int h) { 15 | throw new UnsupportedOperationException("Stub!"); 16 | } 17 | 18 | public static int getUserId(int uid) { 19 | throw new UnsupportedOperationException("Stub!"); 20 | } 21 | 22 | public static int getCallingUserId() { 23 | throw new UnsupportedOperationException("Stub!"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/mirror/android/os/ServiceManager.java: -------------------------------------------------------------------------------- 1 | package mirror.android.os; 2 | 3 | import top.canyie.dreamland.utils.reflect.Reflection; 4 | 5 | /** 6 | * Mirror class of android.os.ServiceManager 7 | * @author canyie 8 | */ 9 | public final class ServiceManager { 10 | public static final String NAME = "android.os.ServiceManager"; 11 | public static final Reflection REF = Reflection.on(NAME); 12 | 13 | public static final Reflection.MethodWrapper getIServiceManager = REF.method("getIServiceManager"); 14 | public static final Reflection.MethodWrapper getService = REF.method("getService", String.class); 15 | public static final Reflection.FieldWrapper sServiceManager = REF.field("sServiceManager"); 16 | 17 | private ServiceManager() { 18 | throw new InstantiationError("Mirror class " + NAME); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/cpp/utils/scoped_elf.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2020/8/5. 3 | // 4 | 5 | #ifndef DREAMLAND_SCOPED_ELF_H 6 | #define DREAMLAND_SCOPED_ELF_H 7 | 8 | #include "macros.h" 9 | #include "../pine.h" 10 | 11 | namespace dreamland { 12 | class ScopedElf { 13 | public: 14 | ScopedElf(const char* elf) : handle_(PineOpenElf(elf)) { 15 | } 16 | 17 | ~ScopedElf() { 18 | PineCloseElf(handle_); 19 | } 20 | 21 | bool GetSymbolAddress(const char* symbol, void** out, bool warn_if_missing = true) { 22 | *out = PineGetElfSymbolAddress(handle_, symbol, warn_if_missing); 23 | return *out != nullptr; 24 | } 25 | 26 | private: 27 | void* handle_; 28 | 29 | DISALLOW_COPY_AND_ASSIGN(ScopedElf); 30 | }; 31 | } 32 | 33 | #endif //DREAMLAND_SCOPED_ELF_H 34 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/content/pm/PackageParser.java: -------------------------------------------------------------------------------- 1 | package android.content.pm; 2 | 3 | import java.io.File; 4 | 5 | public class PackageParser { 6 | public static class PackageLite { 7 | public final String packageName = null; 8 | } 9 | 10 | // Dreamland changed: remove parsePackageLite(String, int) for before sdk 21 11 | // /** Before SDK21 */ 12 | // public static PackageLite parsePackageLite(String packageFile, int flags) { 13 | // throw new UnsupportedOperationException("Stub!"); 14 | // } 15 | 16 | /** Since SDK21 */ 17 | public static PackageLite parsePackageLite(File packageFile, int flags) throws PackageParserException { 18 | throw new UnsupportedOperationException("Stub!"); 19 | } 20 | 21 | /** Since SDK21 */ 22 | @SuppressWarnings("serial") 23 | public static class PackageParserException extends Exception { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hiddenapi-stubs/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 static ActivityThread systemMain() { 20 | throw new UnsupportedOperationException("Stub!"); 21 | } 22 | 23 | public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) { 24 | throw new UnsupportedOperationException("Stub!"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/DreamlandBridge.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland; 2 | 3 | import androidx.annotation.Keep; 4 | import androidx.annotation.NonNull; 5 | 6 | import top.canyie.dreamland.core.Dreamland; 7 | 8 | import java.lang.reflect.Member; 9 | 10 | import top.canyie.pine.Pine; 11 | 12 | /** 13 | * Created by canyie on 2019/11/25. 14 | */ 15 | @Keep @SuppressWarnings("unused") public final class DreamlandBridge { 16 | private DreamlandBridge() { 17 | } 18 | 19 | public static int getDreamlandVersion() { 20 | return Dreamland.VERSION; 21 | } 22 | 23 | public static boolean compileMethod(@NonNull Member method) { 24 | return Pine.compile(method); 25 | } 26 | 27 | public static boolean decompileMethod(@NonNull Member method, boolean disableJit) { 28 | return Pine.decompile(method, disableJit); 29 | } 30 | 31 | public static boolean disableJitInline() { 32 | return Pine.disableJitInline(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/reflect/UncheckedInvocationTargetException.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils.reflect; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.lang.reflect.InvocationTargetException; 6 | 7 | /** 8 | * Created by canyie on 2019/10/24. 9 | */ 10 | public final class UncheckedInvocationTargetException extends ReflectiveException { 11 | public UncheckedInvocationTargetException() { 12 | } 13 | 14 | public UncheckedInvocationTargetException(String message) { 15 | super(message); 16 | } 17 | 18 | public UncheckedInvocationTargetException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public UncheckedInvocationTargetException(String message, Throwable cause) { 23 | super(message, cause); 24 | } 25 | 26 | @NonNull public Throwable getTargetException() { 27 | InvocationTargetException origin = (InvocationTargetException) getCause(); 28 | assert origin != null; 29 | return origin.getTargetException(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/aidl/top/canyie/dreamland/ipc/IDreamlandManager.aidl: -------------------------------------------------------------------------------- 1 | // IDreamlandManager.aidl 2 | package top.canyie.dreamland.ipc; 3 | 4 | import top.canyie.dreamland.ipc.ModuleInfo; 5 | 6 | interface IDreamlandManager { 7 | int getVersion() = 0; 8 | boolean isEnabledFor() = 1; 9 | ModuleInfo[] getEnabledModulesFor(String packageName) = 2; 10 | String[] getAllEnabledModules() = 3; 11 | void setModuleEnabled(String packageName, boolean enabled) = 4; 12 | String[] getEnabledApps() = 5; 13 | void setAppEnabled(String packageName, boolean enabled) = 6; 14 | boolean isSafeModeEnabled() = 7; 15 | void setSafeModeEnabled(boolean enabled) = 8; 16 | void reload() = 9; 17 | boolean isResourcesHookEnabled() = 10; 18 | void setResourcesHookEnabled(boolean enabled) = 11; 19 | boolean isGlobalModeEnabled() = 12; 20 | void setGlobalModeEnabled(boolean enabled) = 13; 21 | String[] getScopeFor(String module) = 14; 22 | void setScopeFor(String module, in String[] scope) = 15; 23 | boolean cannotHookSystemServer() = 16; 24 | } 25 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/Zygote.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.util.Log; 5 | 6 | import java.lang.reflect.Method; 7 | 8 | import top.canyie.dreamland.core.Dreamland; 9 | import top.canyie.dreamland.utils.reflect.Reflection; 10 | 11 | /** 12 | * @author canyie 13 | */ 14 | public final class Zygote { 15 | private static Method allowFileAcrossFork; 16 | private Zygote() {} 17 | 18 | @SuppressLint("BlockedPrivateApi") public static void allowFileAcrossFork(String path) { 19 | try { 20 | if (allowFileAcrossFork == null) { 21 | allowFileAcrossFork = Class.forName("com.android.internal.os.Zygote") 22 | .getDeclaredMethod("nativeAllowFileAcrossFork", String.class); 23 | allowFileAcrossFork.setAccessible(true); 24 | } 25 | allowFileAcrossFork.invoke(null, path); 26 | } catch (Throwable e) { 27 | Log.e(Dreamland.TAG, "Error in nativeAllowFileAcrossFork", e); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | -------------------------------------------------------------------------------- /hiddenapi-stubs/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdk rootProject.ext.compileSdk 5 | namespace 'top.canyie.dreamland.hiddenapi' 6 | defaultConfig { 7 | minSdkVersion rootProject.ext.minSdkVersion 8 | targetSdkVersion rootProject.ext.targetSdkVersion 9 | versionCode rootProject.ext.versionCode 10 | versionName rootProject.ext.versionName 11 | } 12 | 13 | compileOptions { 14 | sourceCompatibility JavaVersion.VERSION_1_8 15 | targetCompatibility JavaVersion.VERSION_1_8 16 | } 17 | } 18 | 19 | dependencies { 20 | def refinePluginVersion = '4.3.0' 21 | annotationProcessor "dev.rikka.tools.refine:annotation-processor:$refinePluginVersion" 22 | compileOnly "dev.rikka.tools.refine:annotation:$refinePluginVersion" 23 | } 24 | 25 | tasks.register('jarStubApis', Jar) { 26 | dependsOn build 27 | archiveBaseName = 'hiddenapis-stub' 28 | from("$projectDir/build/intermediates/javac/release/classes/") 29 | destinationDirectory = layout.buildDirectory.dir("$rootDir/app/lib/") 30 | exclude 'BuildConfig.class', 'R.class' 31 | exclude { it.name.startsWith('R$') } 32 | } 33 | -------------------------------------------------------------------------------- /template/service.sh: -------------------------------------------------------------------------------- 1 | #!/system/bin/sh 2 | MODDIR=${0%/*} 3 | DATADIR=/data/misc/dreamland 4 | [ -f "$DATADIR/disable_reason" ] && mv -f "$DATADIR/disable_reason" "$DATADIR/disable" 5 | 6 | [ -f "$DATADIR/disable" ] && exit 0 7 | 8 | if [ -f "$DATADIR/verbose_logcat" ]; then 9 | now=$(date "+%Y%m%d%H%M%S") 10 | logcat > "/data/local/tmp/log_$now.log" & 11 | fi 12 | 13 | [ -f "$DATADIR/bootloop_protection" ] || exit 0 14 | 15 | MAIN_ZYGOTE_NICENAME=zygote 16 | CPU_ABI=$(getprop ro.product.cpu.api) 17 | [ "$CPU_ABI" = "arm64-v8a" ] && MAIN_ZYGOTE_NICENAME=zygote64 18 | 19 | # Wait for zygote starts 20 | sleep 5 21 | 22 | ZYGOTE_PID1=$(pidof "$MAIN_ZYGOTE_NICENAME") 23 | sleep 15 24 | ZYGOTE_PID2=$(pidof "$MAIN_ZYGOTE_NICENAME") 25 | sleep 15 26 | ZYGOTE_PID3=$(pidof "$MAIN_ZYGOTE_NICENAME") 27 | 28 | [ "$ZYGOTE_PID1" = "$ZYGOTE_PID2" ] && [ "$ZYGOTE_PID2" = "$ZYGOTE_PID3" ] && exit 0 29 | 30 | sleep 15 31 | ZYGOTE_PID4=$(pidof "$MAIN_ZYGOTE_NICENAME") 32 | [ "$ZYGOTE_PID3" = "$ZYGOTE_PID4" ] && exit 0 33 | 34 | # Zygote keeps restarting in 50s, disable framework and restart zygote 35 | 36 | echo "Bootloop protection: zygote keeps restarting in 50s" >> $DATADIR/disable 37 | setprop ctl.restart zygote 38 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/RuntimeUtils.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.lang.reflect.Modifier; 6 | 7 | import top.canyie.dreamland.utils.reflect.Reflection; 8 | 9 | /** 10 | * @author canyie 11 | */ 12 | public final class RuntimeUtils { 13 | private static final Reflection.FieldWrapper accessFlags = Reflection.field(Class.class, "accessFlags"); 14 | private static final Reflection.FieldWrapper parent = Reflection.field(ClassLoader.class, "parent"); 15 | 16 | private RuntimeUtils() {} 17 | 18 | public static void makeExtendable(Class c) { 19 | int modifiers = c.getModifiers(); 20 | if (Modifier.isFinal(modifiers) || !Modifier.isPublic(modifiers)) { 21 | int flags = accessFlags.getValue(c); 22 | flags &= ~Modifier.FINAL; 23 | flags &= ~(Modifier.PRIVATE | Modifier.PROTECTED); 24 | flags |= Modifier.PUBLIC; 25 | accessFlags.setValue(c, flags); 26 | } 27 | } 28 | 29 | public static void setParent(@NonNull ClassLoader target, @NonNull ClassLoader newParent) { 30 | parent.setValue(target, newParent); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | /export/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Log Files 30 | *.log 31 | 32 | # Android Studio Navigation editor temp files 33 | .navigation/ 34 | 35 | # Android Studio captures folder 36 | captures/ 37 | 38 | # IntelliJ 39 | *.iml 40 | .idea/ 41 | 42 | # Keystore files 43 | *.jks 44 | *.keystore 45 | 46 | # External native build folder generated in Android Studio 2.2 and later 47 | .externalNativeBuild 48 | .cxx/ 49 | 50 | # Google Services (e.g. APIs or Firebase) 51 | # google-services.json 52 | 53 | # Freeline 54 | freeline.py 55 | freeline/ 56 | freeline_project_description.json 57 | 58 | # fastlane 59 | fastlane/report.xml 60 | fastlane/Preview.html 61 | fastlane/screenshots 62 | fastlane/test_output 63 | fastlane/readme.md 64 | 65 | # Version control 66 | vcs.xml 67 | 68 | # lint 69 | lint/intermediates/ 70 | lint/generated/ 71 | lint/outputs/ 72 | lint/tmp/ 73 | 74 | .DS_Store -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/resources_hook.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2020/8/5. 3 | // 4 | 5 | #ifndef DREAMLAND_RESOURCES_HOOK_H 6 | #define DREAMLAND_RESOURCES_HOOK_H 7 | 8 | #include 9 | #include 10 | #include "resource_types.h" 11 | 12 | namespace dreamland { 13 | class ResourcesHook { 14 | public: 15 | static bool Init(JNIEnv* env, jobject classLoader); 16 | static void JNI_rewriteXmlReferencesNative(JNIEnv *env, jclass, 17 | jlong parserPtr, jobject origRes, jobject repRes); 18 | private: 19 | static int32_t (*ResXMLParser_next)(void*); 20 | static int32_t (*ResXMLParser_restart)(void*); 21 | static int32_t (*ResXMLParser_getAttributeNameID)(void*, int); 22 | static char16_t* (*ResStringPool_stringAt)(const void*, int32_t, size_t*); 23 | static android::expected (*ResStringPool_stringAtS)( 24 | const void*, size_t); 25 | static jclass XResources; 26 | static jmethodID translateResId; 27 | static jmethodID translateAttrId; 28 | 29 | static constexpr const char* kXResourcesClassName = "android.content.res.XResources"; 30 | }; 31 | } 32 | 33 | #endif //DREAMLAND_RESOURCES_HOOK_H 34 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Android 2 | # Build your Android project with Gradle. 3 | # Add steps that test, sign, and distribute the APK, save build artifacts, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/android 5 | 6 | trigger: 7 | - master 8 | 9 | pool: 10 | vmImage: 'macos-latest' 11 | 12 | steps: 13 | - task: Gradle@3 14 | inputs: 15 | workingDirectory: '' 16 | gradleWrapperFile: 'gradlew' 17 | gradleOptions: '-Xmx3072m' 18 | javaHomeOption: 'JDKVersion' 19 | jdkVersionOption: '1.17' 20 | publishJUnitResults: false 21 | tasks: 'assembleMagiskDebug assembleMagiskRelease' 22 | 23 | - task: CopyFiles@2 24 | inputs: 25 | contents: '**/build/outputs/magisk/dreamland-**.zip' 26 | targetFolder: '$(build.artifactStagingDirectory)' 27 | cleanTargetFolder: true 28 | flattenFolders: true 29 | 30 | - task: CopyFiles@2 31 | inputs: 32 | contents: '**/build/outputs/mapping/release/**.txt' 33 | targetFolder: '$(build.artifactStagingDirectory)' 34 | cleanTargetFolder: false 35 | flattenFolders: true 36 | 37 | - task: PublishBuildArtifacts@1 38 | inputs: 39 | pathToPublish: '$(build.artifactStagingDirectory)' 40 | artifactName: 'dreamland-$(build.buildId).zip' 41 | artifactType: 'container' 42 | 43 | pr: none 44 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/BuildUtils.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils; 2 | 3 | import android.os.Build; 4 | 5 | import java.util.Locale; 6 | 7 | import static android.os.Build.VERSION.SDK_INT; 8 | import static android.os.Build.VERSION_CODES.S; 9 | import static android.os.Build.VERSION_CODES.S_V2; 10 | 11 | /** 12 | * @author canyie 13 | */ 14 | public final class BuildUtils { 15 | private BuildUtils() {} 16 | 17 | public static boolean isAtLeastT() { 18 | return SDK_INT > S_V2 || (SDK_INT == S_V2 && isAtLeastPreReleaseCodename("Tiramisu")); 19 | } 20 | 21 | public static boolean isAlLeastSv2() { 22 | return SDK_INT >= S_V2 || (SDK_INT == S && isAtLeastPreReleaseCodename("Sv2")); 23 | } 24 | 25 | // https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:core/core/src/main/java/androidx/core/os/BuildCompat.java;l=49;drc=f8ab4c3030c3fbadca32a9593c522c89a9f2cadf 26 | private static boolean isAtLeastPreReleaseCodename(String codename) { 27 | final String buildCodename = Build.VERSION.CODENAME.toUpperCase(Locale.ROOT); 28 | 29 | // Special case "REL", which means the build is not a pre-release build. 30 | if ("REL".equals(buildCodename)) { 31 | return false; 32 | } 33 | 34 | return buildCodename.compareTo(codename.toUpperCase(Locale.ROOT)) >= 0; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/cpp/utils/well_known_classes.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2019/11/17. 3 | // 4 | 5 | #ifndef DREAMLAND_WELL_KNOWN_CLASSES_H 6 | #define DREAMLAND_WELL_KNOWN_CLASSES_H 7 | 8 | #include 9 | #include "log.h" 10 | #include "macros.h" 11 | 12 | namespace dreamland { 13 | class WellKnownClasses { 14 | public: 15 | //static jclass java_lang_Object; 16 | //static jclass java_lang_Class; 17 | static jclass java_lang_ClassLoader; 18 | static jclass java_lang_String; 19 | //static jclass java_lang_Thread; 20 | 21 | static jmethodID java_lang_ClassLoader_loadClass; 22 | static jmethodID java_lang_ClassLoader_getSystemClassLoader; 23 | 24 | static void Init(JNIEnv *env); 25 | 26 | static void Clear(JNIEnv *env); 27 | 28 | static jclass CacheClass(JNIEnv *env, const char *name); 29 | static jmethodID 30 | CacheMethod(JNIEnv *env, jclass klass, const char *name, const char *signature, 31 | bool is_static); 32 | private: 33 | static bool inited; 34 | 35 | 36 | 37 | static void ClearGlobalReference(JNIEnv *env, jclass *ptr) { 38 | env->DeleteGlobalRef(*ptr); 39 | *ptr = nullptr; 40 | } 41 | 42 | DISALLOW_IMPLICIT_CONSTRUCTORS(WellKnownClasses); 43 | }; 44 | } 45 | 46 | #endif //DREAMLAND_WELL_KNOWN_CLASSES_H 47 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/binder.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2021/1/28. 3 | // 4 | 5 | #ifndef DREAMLAND_BINDER_H 6 | #define DREAMLAND_BINDER_H 7 | 8 | 9 | #include 10 | 11 | namespace dreamland { 12 | class Binder { 13 | public: 14 | static bool Prepare(JNIEnv* env); 15 | static jobject GetBinder(JNIEnv* env); 16 | static void Cleanup(JNIEnv* env); 17 | 18 | private: 19 | static void RecycleParcel(JNIEnv* env, jobject parcel) { 20 | if (parcel) { 21 | env->CallVoidMethod(parcel, recycleParcel); 22 | env->ExceptionClear(); 23 | } 24 | } 25 | 26 | static jclass ServiceManager; 27 | static jclass IBinder; 28 | static jclass Parcel; 29 | static jmethodID getService; 30 | static jmethodID transact; 31 | static jmethodID obtainParcel; 32 | static jmethodID writeInterfaceToken; 33 | static jmethodID readException; 34 | static jmethodID readStrongBinder; 35 | static jmethodID recycleParcel; 36 | static jstring serviceName; 37 | static jstring interfaceToken; 38 | 39 | static constexpr const char* kServiceName = "clipboard"; 40 | static constexpr const char* kInterfaceToken = "android.content.IClipboard"; 41 | static constexpr jint kTransactionCode = ('_'<<24)|('D'<<16)|('M'<<8)|'S'; 42 | }; 43 | } 44 | 45 | 46 | #endif //DREAMLAND_BINDER_H 47 | -------------------------------------------------------------------------------- /app/src/main/cpp/utils/log.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2019/11/11. 3 | // 4 | 5 | #ifndef DREAMLAND_LOG_H 6 | #define DREAMLAND_LOG_H 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | constexpr const char *LOG_TAG = "Dreamland"; 11 | #else 12 | #define LOG_TAG "Dreamland" 13 | #endif 14 | 15 | #include 16 | 17 | 18 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 19 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 20 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 21 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 22 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 23 | #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) 24 | 25 | #define FATAL(...) do {\ 26 | LOGF("*** Runtime aborting because of fatal error: ");\ 27 | LOGF(__VA_ARGS__);\ 28 | LOGF("Aborting...");\ 29 | abort();\ 30 | } while(0) 31 | 32 | #define FATAL_FOR_JNI(...) do {\ 33 | LOGF("*** Runtime aborting because of fatal error: ");\ 34 | LOGF(__VA_ARGS__);\ 35 | if(((env)->ExceptionCheck())) {\ 36 | LOGF("JNI ERROR: ");\ 37 | (env)->ExceptionDescribe();\ 38 | }\ 39 | env->FatalError("FATAL_FOR_JNI called.");\ 40 | } while(0) 41 | 42 | #define CHECK(op, ...) do { if((!(op))) { FATAL(__VA_ARGS__); } } while(0) 43 | #define CHECK_FOR_JNI(op, ...) do { if((!(op))) { FATAL_FOR_JNI(__VA_ARGS__); } } while(0) 44 | 45 | #ifdef __cplusplus 46 | } 47 | #endif 48 | 49 | 50 | #endif //DREAMLAND_LOG_H 51 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/content/pm/IPackageManager.java: -------------------------------------------------------------------------------- 1 | package android.content.pm; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Binder; 5 | import android.os.IBinder; 6 | import android.os.IInterface; 7 | import android.os.RemoteException; 8 | 9 | /** 10 | * @author canyie 11 | */ 12 | public interface IPackageManager extends IInterface { 13 | ApplicationInfo getApplicationInfo(String packageName, int flags, int userId) throws RemoteException; 14 | 15 | @TargetApi(33) 16 | ApplicationInfo getApplicationInfo(String packageName, long flags, int userId) 17 | throws RemoteException; 18 | 19 | PackageInfo getPackageInfo(String packageName, int flags, int userId) throws RemoteException; 20 | 21 | @TargetApi(33) 22 | PackageInfo getPackageInfo(String packageName, long flags, int userId) throws RemoteException; 23 | 24 | int getPackageUid(String packageName, int userId) throws RemoteException; 25 | 26 | int getPackageUid(String packageName, int flags, int userId) throws RemoteException; 27 | 28 | @TargetApi(33) 29 | int getPackageUid(String packageName, long flags, int userId) throws RemoteException; 30 | 31 | String[] getPackagesForUid(int uid) throws RemoteException; 32 | 33 | String getNameForUid(int uid) throws RemoteException; 34 | 35 | abstract class Stub extends Binder implements IPackageManager { 36 | public static IPackageManager asInterface(IBinder service) { 37 | throw new UnsupportedOperationException("Stub!"); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/IXposedHookInitPackageResources.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed; 2 | 3 | import android.content.res.XResources; 4 | 5 | import de.robv.android.xposed.callbacks.XC_InitPackageResources; 6 | import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam; 7 | 8 | /** 9 | * Get notified when the resources for an app are initialized. 10 | * In {@link #handleInitPackageResources}, resource replacements can be created. 11 | * 12 | *

This interface should be implemented by the module's main class. Xposed will take care of 13 | * registering it as a callback automatically. 14 | */ 15 | public interface IXposedHookInitPackageResources extends IXposedMod { 16 | /** 17 | * This method is called when resources for an app are being initialized. 18 | * Modules can call special methods of the {@link XResources} class in order to replace resources. 19 | * 20 | * @param resparam Information about the resources. 21 | * @throws Throwable Everything the callback throws is caught and logged. 22 | */ 23 | void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable; 24 | 25 | /** @hide */ 26 | final class Wrapper extends XC_InitPackageResources { 27 | private final IXposedHookInitPackageResources instance; 28 | public Wrapper(IXposedHookInitPackageResources instance) { 29 | this.instance = instance; 30 | } 31 | @Override 32 | public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable { 33 | instance.handleInitPackageResources(resparam); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/zygisk_flavor.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2022/2/28. 3 | // 4 | 5 | #include "flavor.h" 6 | #include "../zygisk.hpp" 7 | #include "../utils/macros.h" 8 | 9 | using namespace dreamland; 10 | 11 | using zygisk::Api; 12 | using zygisk::AppSpecializeArgs; 13 | using zygisk::ServerSpecializeArgs; 14 | 15 | class DreamlandZygiskFlavor : public zygisk::ModuleBase { 16 | public: 17 | void onLoad(Api* api, JNIEnv* env) override { 18 | this->api_ = api; 19 | this->env_ = env; 20 | skip_ = true; 21 | Flavor::OnModuleLoaded(false); 22 | } 23 | 24 | void preAppSpecialize(AppSpecializeArgs* args) override { 25 | skip_ = Flavor::ShouldSkip(args->is_child_zygote && *args->is_child_zygote, args->uid); 26 | if (!skip_) 27 | Flavor::PreFork(env_, true); 28 | } 29 | 30 | void postAppSpecialize(const AppSpecializeArgs* args) override { 31 | /** FIXME: check if the zygote that created the child process is main zygote */ 32 | if (skip_ || !Flavor::PostForkApp(env_, false)) 33 | api_->setOption(zygisk::Option::DLCLOSE_MODULE_LIBRARY); 34 | } 35 | 36 | void preServerSpecialize(ServerSpecializeArgs* args) override { 37 | if (LIKELY(!Flavor::IsDisabled())) Flavor::PreFork(env_, true); 38 | } 39 | 40 | void postServerSpecialize(const ServerSpecializeArgs* args) override { 41 | if (!Flavor::IsDisabled()) Flavor::PostForkSystemServer(env_); 42 | } 43 | 44 | private: 45 | Api *api_; 46 | JNIEnv *env_; 47 | bool skip_; 48 | }; 49 | 50 | REGISTER_ZYGISK_MODULE(DreamlandZygiskFlavor) 51 | -------------------------------------------------------------------------------- /app/src/main/cpp/utils/macros.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2019/11/18. 3 | // 4 | 5 | #ifndef DREAMLAND_MACROS_H 6 | #define DREAMLAND_MACROS_H 7 | 8 | #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) 9 | 10 | #define LIKELY(x) __builtin_expect(!!(x), 1) 11 | #define UNLIKELY(x) __builtin_expect(!!(x), 0) 12 | 13 | #ifdef __LP64__ 14 | #define LP_SELECT(lp64, lp32) (lp64) 15 | #else 16 | #define LP_SELECT(lp64, lp32) (lp32) 17 | #endif 18 | 19 | // From Android Open Source Project: 20 | 21 | // A macro to disallow the copy constructor and operator= functions 22 | // This must be placed in the private: declarations for a class. 23 | // 24 | // For disallowing only assign or copy, delete the relevant operator or 25 | // constructor, for example: 26 | // void operator=(const TypeName&) = delete; 27 | // Note, that most uses of DISALLOW_ASSIGN and DISALLOW_COPY are broken 28 | // semantically, one should either use disallow both or neither. Try to 29 | // avoid these in new code. 30 | #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ 31 | TypeName(const TypeName&) = delete; \ 32 | void operator=(const TypeName&) = delete 33 | 34 | // A macro to disallow all the implicit constructors, namely the 35 | // default constructor, copy constructor and operator= functions. 36 | // 37 | // This should be used in the private: declarations for a class 38 | // that wants to prevent anyone from instantiating it. This is 39 | // especially useful for classes containing only static methods. 40 | #define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ 41 | TypeName() = delete; \ 42 | DISALLOW_COPY_AND_ASSIGN(TypeName) 43 | 44 | #endif //DREAMLAND_MACROS_H 45 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/core/GsonBasedManager.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.core; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import top.canyie.dreamland.utils.AppGlobals; 7 | import top.canyie.dreamland.utils.DLog; 8 | import com.google.gson.JsonSyntaxException; 9 | 10 | import java.lang.reflect.ParameterizedType; 11 | import java.lang.reflect.Type; 12 | 13 | /** 14 | * @author canyie 15 | */ 16 | @SuppressWarnings("WeakerAccess") 17 | public abstract class GsonBasedManager extends BaseManager { 18 | private static final String TAG = "GsonBasedManager"; 19 | private final Type mType; 20 | 21 | protected GsonBasedManager(String filename) { 22 | super(filename); 23 | 24 | Type genericSuperclass = getClass().getGenericSuperclass(); 25 | if (!(genericSuperclass instanceof ParameterizedType)) { 26 | throw new RuntimeException("Missing type parameter."); 27 | } 28 | Type type = ((ParameterizedType) genericSuperclass).getActualTypeArguments()[0]; 29 | assert type != null; 30 | mType = type; 31 | } 32 | 33 | @NonNull @Override protected String serialize(T obj) { 34 | return AppGlobals.getGson().toJson(obj); 35 | } 36 | 37 | @Nullable @Override protected T deserialize(String str) { 38 | try { 39 | return AppGlobals.getGson().fromJson(str, mType); 40 | } catch (JsonSyntaxException e) { 41 | DLog.e(TAG, "!!! JsonSyntaxException threw in deserialize !!!", e); 42 | DLog.e(TAG, "json content: %s", str); 43 | } 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/mirror/android/app/ActivityThread.java: -------------------------------------------------------------------------------- 1 | package mirror.android.app; 2 | 3 | import top.canyie.dreamland.utils.reflect.Reflection; 4 | import top.canyie.dreamland.utils.reflect.Reflection.MethodWrapper; 5 | import top.canyie.dreamland.utils.reflect.Reflection.FieldWrapper; 6 | 7 | /** 8 | * Mirror class of android.app.ActivityThread 9 | * @author canyie 10 | */ 11 | @SuppressWarnings({"unused"}) 12 | public final class ActivityThread { 13 | public static final String NAME = "android.app.ActivityThread"; 14 | public static final Reflection REF = Reflection.on(NAME); 15 | 16 | //public static final MethodWrapper currentActivityThread = REF.method("currentActivityThread"); 17 | //public static final MethodWrapper getApplication = REF.method("getApplication"); 18 | public static final FieldWrapper mBoundApplication = REF.field("mBoundApplication"); 19 | 20 | private ActivityThread() { 21 | throw new InstantiationError("Mirror class " + NAME); 22 | } 23 | 24 | /** 25 | * Mirror class of android.app.ActivityThread.AppBindData 26 | */ 27 | public static final class AppBindData { 28 | public static final String NAME = "android.app.ActivityThread$AppBindData"; 29 | public static final Reflection REF = Reflection.on(NAME); 30 | 31 | public static final FieldWrapper appInfo = REF.field("appInfo"); 32 | public static final FieldWrapper processName = REF.field("processName"); 33 | public static final FieldWrapper compatInfo = REF.field("compatInfo"); 34 | 35 | private AppBindData() { 36 | throw new InstantiationError("Mirror class " + NAME); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/callbacks/XC_InitPackageResources.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.callbacks; 2 | 3 | import android.content.res.XResources; 4 | 5 | import de.robv.android.xposed.IXposedHookInitPackageResources; 6 | import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet; 7 | 8 | /** 9 | * This class is only used for internal purposes, except for the {@link InitPackageResourcesParam} 10 | * subclass. 11 | */ 12 | public abstract class XC_InitPackageResources extends XCallback implements IXposedHookInitPackageResources { 13 | /** 14 | * Creates a new callback with default priority. 15 | * @hide 16 | */ 17 | @SuppressWarnings("deprecation") 18 | public XC_InitPackageResources() { 19 | super(); 20 | } 21 | 22 | /** 23 | * Creates a new callback with a specific priority. 24 | * 25 | * @param priority See {@link XCallback#priority}. 26 | * @hide 27 | */ 28 | public XC_InitPackageResources(int priority) { 29 | super(priority); 30 | } 31 | 32 | /** 33 | * Wraps information about the resources being initialized. 34 | */ 35 | public static final class InitPackageResourcesParam extends Param { 36 | /** @hide */ 37 | public InitPackageResourcesParam(CopyOnWriteSortedSet callbacks) { 38 | super(callbacks); 39 | } 40 | 41 | /** The name of the package for which resources are being loaded. */ 42 | public String packageName; 43 | 44 | /** 45 | * Reference to the resources that can be used for calls to 46 | * {@link XResources#setReplacement(String, String, String, Object)}. 47 | */ 48 | public XResources res; 49 | } 50 | 51 | /** @hide */ 52 | @Override 53 | protected void call(Param param) throws Throwable { 54 | if (param instanceof InitPackageResourcesParam) 55 | handleInitPackageResources((InitPackageResourcesParam) param); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/Preconditions.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils; 2 | 3 | import android.text.TextUtils; 4 | 5 | /** 6 | * Created by canyie on 2019/10/24. 7 | */ 8 | public final class Preconditions { 9 | private Preconditions() {} 10 | 11 | public static T checkNotNull(T value) { 12 | if(value == null) { 13 | throw new NullPointerException(); 14 | } 15 | return value; 16 | } 17 | 18 | public static T checkNotNull(T value, String errorMsg) { 19 | if(value == null) { 20 | throw new NullPointerException(errorMsg); 21 | } 22 | return value; 23 | } 24 | 25 | public static void checkArgument(boolean valid) { 26 | if(!valid) { 27 | throw new IllegalArgumentException(); 28 | } 29 | } 30 | 31 | public static void checkArgument(boolean valid,String errorMsg) { 32 | if(!valid) { 33 | throw new IllegalArgumentException(errorMsg); 34 | } 35 | } 36 | 37 | public static void checkState(boolean valid) { 38 | if(!valid) { 39 | throw new IllegalStateException(); 40 | } 41 | } 42 | 43 | public static void checkState(boolean valid,String errorMsg) { 44 | if(!valid) { 45 | throw new IllegalStateException(errorMsg); 46 | } 47 | } 48 | 49 | public static T checkNotEmpty(T value) { 50 | if(TextUtils.isEmpty(value)) { 51 | throw new IllegalArgumentException(); 52 | } 53 | return value; 54 | } 55 | 56 | public static T checkNotEmpty(T value, String errorMsg) { 57 | if(TextUtils.isEmpty(value)) { 58 | throw new IllegalArgumentException(errorMsg); 59 | } 60 | return value; 61 | } 62 | } -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/os/SELinux.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | /** 4 | * @author canyie 5 | */ 6 | public final class SELinux { 7 | private SELinux() {} 8 | 9 | /** 10 | * Determine whether SELinux is disabled or enabled. 11 | * @return a boolean indicating whether SELinux is enabled. 12 | */ 13 | public static boolean isSELinuxEnabled() { 14 | throw new UnsupportedOperationException("Stub!"); 15 | } 16 | 17 | /** 18 | * Determine whether SELinux is permissive or enforcing. 19 | * @return a boolean indicating whether SELinux is enforcing. 20 | */ 21 | public static boolean isSELinuxEnforced() { 22 | throw new UnsupportedOperationException("Stub!"); 23 | } 24 | 25 | /** 26 | * Gets the security context of the current process. 27 | * @return a String representing the security context of the current process. 28 | */ 29 | public static String getContext() { 30 | throw new UnsupportedOperationException("Stub!"); 31 | } 32 | 33 | /** 34 | * Gets the security context of a given process id. 35 | * @param pid an int representing the process id to check. 36 | * @return a String representing the security context of the given pid. 37 | */ 38 | public static String getPidContext(int pid) { 39 | throw new UnsupportedOperationException("Stub!"); 40 | } 41 | 42 | /** 43 | * Check permissions between two security contexts. 44 | * @param scon The source or subject security context. 45 | * @param tcon The target or object security context. 46 | * @param tclass The object security class name. 47 | * @param perm The permission name. 48 | * @return a boolean indicating whether permission was granted. 49 | */ 50 | public static boolean checkSELinuxAccess(String scon, String tcon, String tclass, String perm) { 51 | throw new UnsupportedOperationException("Stub!"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/services/FileResult.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.services; 2 | 3 | import java.io.InputStream; 4 | 5 | /** 6 | * Holder for the result of a {@link BaseService#readFile} or {@link BaseService#statFile} call. 7 | */ 8 | public final class FileResult { 9 | /** File content, might be {@code null} if the file wasn't read. */ 10 | public final byte[] content; 11 | /** File input stream, might be {@code null} if the file wasn't read. */ 12 | public final InputStream stream; 13 | /** File size. */ 14 | public final long size; 15 | /** File last modification time. */ 16 | public final long mtime; 17 | 18 | /*package*/ FileResult(long size, long mtime) { 19 | this.content = null; 20 | this.stream = null; 21 | this.size = size; 22 | this.mtime = mtime; 23 | } 24 | 25 | /*package*/ FileResult(byte[] content, long size, long mtime) { 26 | this.content = content; 27 | this.stream = null; 28 | this.size = size; 29 | this.mtime = mtime; 30 | } 31 | 32 | /*package*/ FileResult(InputStream stream, long size, long mtime) { 33 | this.content = null; 34 | this.stream = stream; 35 | this.size = size; 36 | this.mtime = mtime; 37 | } 38 | 39 | /** @hide */ 40 | @Override 41 | public String toString() { 42 | StringBuilder sb = new StringBuilder("{"); 43 | if (content != null) { 44 | sb.append("content.length: "); 45 | sb.append(content.length); 46 | sb.append(", "); 47 | } 48 | if (stream != null) { 49 | sb.append("stream: "); 50 | sb.append(stream.toString()); 51 | sb.append(", "); 52 | } 53 | sb.append("size: "); 54 | sb.append(size); 55 | sb.append(", mtime: "); 56 | sb.append(mtime); 57 | sb.append("}"); 58 | return sb.toString(); 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/cpp/utils/scoped_local_ref.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2019/11/17. 3 | // 4 | 5 | #ifndef DREAMLAND_SCOPED_LOCAL_REF_H 6 | #define DREAMLAND_SCOPED_LOCAL_REF_H 7 | 8 | #include "macros.h" 9 | 10 | template 11 | class ScopedLocalRef { 12 | public: 13 | ScopedLocalRef(JNIEnv *env) : env(env), mLocalRef(nullptr) { 14 | } 15 | 16 | ScopedLocalRef(JNIEnv *env, T localRef) : env(env), mLocalRef(localRef) { 17 | } 18 | 19 | ScopedLocalRef(JNIEnv *env, const char* content) : ScopedLocalRef(env, env->NewStringUTF(content)) { 20 | } 21 | 22 | ~ScopedLocalRef() { 23 | Reset(); 24 | } 25 | 26 | T Get() const { 27 | return mLocalRef; 28 | } 29 | 30 | void Reset(T newRef = nullptr) { 31 | if (mLocalRef != newRef) { 32 | if (mLocalRef != nullptr) { 33 | env->DeleteLocalRef(mLocalRef); 34 | } 35 | mLocalRef = newRef; 36 | } 37 | } 38 | 39 | T Release() __attribute__((warn_unused_result)) { 40 | T ref = mLocalRef; 41 | mLocalRef = nullptr; 42 | return ref; 43 | } 44 | 45 | bool IsNull() { 46 | return mLocalRef == nullptr; 47 | } 48 | 49 | ScopedLocalRef& operator=(ScopedLocalRef&& s) noexcept { 50 | Reset(s.Release()); 51 | env = s.env; 52 | return *this; 53 | } 54 | 55 | bool operator==(std::nullptr_t) { 56 | return IsNull(); 57 | } 58 | 59 | bool operator!=(std::nullptr_t) { 60 | return !IsNull(); 61 | } 62 | 63 | bool operator==(ScopedLocalRef const s) { 64 | return env->IsSameObject(mLocalRef, s.mLocalRef); 65 | } 66 | 67 | bool operator!=(ScopedLocalRef const s) { 68 | return !env->IsSameObject(mLocalRef, s.mLocalRef); 69 | } 70 | 71 | private: 72 | JNIEnv *env; 73 | T mLocalRef; 74 | 75 | DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef); 76 | }; 77 | 78 | #endif //DREAMLAND_SCOPED_LOCAL_REF_H 79 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/flavor.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2022/2/28. 3 | // 4 | 5 | #include "flavor.h" 6 | #include "dreamland.h" 7 | #include "../pine.h" 8 | 9 | using namespace dreamland; 10 | 11 | static bool disabled = false; 12 | 13 | void Flavor::OnModuleLoaded(bool zygote) { 14 | // zygote == false means the flavor is Zygisk, which will call OnModuleLoaded() from app process 15 | // rather than zygote, apps can read logs printed by their processes so this can be detected 16 | if (zygote) LOGI("Welcome to Dreamland %s (%d)!", Dreamland::VERSION_NAME, Dreamland::VERSION); 17 | disabled = Dreamland::ShouldDisable(); 18 | if (UNLIKELY(disabled)) { 19 | if (zygote) LOGW("Dreamland framework should be disabled, do nothing."); 20 | return; 21 | } 22 | Android::Initialize(); 23 | int api_level = Android::version; 24 | if (zygote) LOGI("Android Api Level %d", api_level); 25 | PineSetAndroidVersion(api_level); 26 | Dreamland::Prepare(true); 27 | } 28 | 29 | bool Flavor::IsDisabled() { 30 | return disabled; 31 | } 32 | 33 | bool Flavor::ShouldSkip(bool is_child_zygote, int uid) { 34 | if (UNLIKELY(disabled)) return true; 35 | 36 | if (UNLIKELY(Dreamland::ShouldSkipUid(uid))) { 37 | LOGW("Skipping this process because it is isolated service, RELTO updater or webview zygote"); 38 | return true; 39 | } 40 | 41 | if (UNLIKELY(is_child_zygote)) { 42 | // child zygote is not allowed to do binder transaction, so our binder call will crash it 43 | LOGW("Skipping this process because it is a child zygote"); 44 | return true; 45 | } 46 | return false; 47 | } 48 | 49 | void Flavor::PreFork(JNIEnv* env, bool zygote) { 50 | Dreamland::PrepareJava(env, zygote); 51 | } 52 | 53 | void Flavor::PostForkSystemServer(JNIEnv* env) { 54 | Dreamland::OnSystemServerStart(env); 55 | } 56 | 57 | bool Flavor::PostForkApp(JNIEnv* env, bool main_zygote) { 58 | return Dreamland::OnAppProcessStart(env, main_zygote); 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # 梦境框架 [![Build Status](https://dev.azure.com/ssz33334930121/ssz3333493/_apis/build/status/canyie.Dreamland?branchName=master)](https://dev.azure.com/ssz33334930121/ssz3333493/_build/latest?definitionId=1&branchName=master) 2 | 3 | [English](README.md) 4 | 5 | ## 简介 6 | 梦境是另一种 Xposed 框架实现,你可以借助她直接使用 Xposed 模块。 7 | - 支持 Android 5.0~**14** 8 | - 低侵入性 9 | - 对于 hook 非系统应用(及部分系统应用),只需要目标应用重启而非整个设备 10 | - 严格限制模块,你可以选择哪些应用需要加载哪些模块 11 | 12 | **注:目前我没有足够的时间和精力去维护这个项目,此项目接下来的开发将不活跃(但不会停止)。其他成熟的框架实现(如 [LSPosed](https://github.com/LSPosed/LSPosed) 和 [太极](https://taichi.cool/))会是好的替代品。** 13 | 14 | ## 警告 15 | **安装梦境是危险的; 无论如何,请自己备份好数据并做好设备完全损坏的准备,我们不对您的任何损失负责。** 16 | 17 | ## 安装 18 | 1. 安装 Magisk (推荐最新版本). 19 | 2. 安装 [Riru](https://github.com/RikkaApps/Riru) 或在 Magisk 设置中启用 Zygisk 并重启。如果您使用 Riru,我们建议您使用较新版本的 Riru,v21 及更低版本容易被发现。 20 | 3. 下载 Dreamland,然后在 Magisk app 中刷入它。从自定义 Recovery 刷入不再是受支持的安装方式。 21 | 4. 安装 [Dreamland Manager](https://github.com/canyie/DreamlandManager/releases)。 22 | 5. 重启。(对于 Magisk < 21006, 你需要重启至少两次。) 23 | 24 | ## 下载通道 25 | - Beta: 测试版本,由开发者发布。从我们的 [GitHub Release](https://github.com/canyie/Dreamland/releases) 中下载。 26 | - Canary: 测试版本,由 CI 自动构建,使用风险自负。可以在 [Azure Pipelines](https://dev.azure.com/ssz33334930121/ssz3333493/_build/latest?definitionId=1&branchName=master) 下载到。 27 | 28 | ## 已知问题 29 | - Thanox 模块不工作。请勿使用 Thanox 模块否则你的设备会开不了机。 30 | - [New XSharedPreferences](https://github.com/LSPosed/LSPosed/wiki/New-XSharedPreferences) 未实现在梦境中,需要该功能的模块不会工作。我们正在计划一个叫做 "XRemotePreferences" 的新 API,它稳定、实现漂亮、易于维护,请至[此页面](https://github.com/libxposed/XposedService/issues/1) 了解更多并与我们交流。 31 | 32 | ## 交流 33 | - [QQ 群:949888394](https://shang.qq.com/wpa/qunwpa?idkey=25549719b948d2aaeb9e579955e39d71768111844b370fcb824d43b9b20e1c04) 34 | - [Telegram 群:@DreamlandFramework](https://t.me/DreamlandFramework) 35 | 36 | ## 鸣谢 37 | - [Xposed](https://github.com/rovo89/Xposed): 原版 Xposed 框架 38 | - [EdXposed](https://github.com/ElderDrivers/EdXposed) 39 | - [LSPosed](https://github.com/LSPosed/LSPosed): 另一个现代化的 Xposed 框架 40 | - [Magisk](https://github.com/topjohnwu/Magisk/): 让这一切成为可能 41 | - [Riru](https://github.com/RikkaApps/Riru): 提供一种方法注入 zygote 进程 42 | - [Pine](https://github.com/canyie/pine): ART hook 库 43 | -------------------------------------------------------------------------------- /app/src/main/java/android/content/res/XModuleResources.java: -------------------------------------------------------------------------------- 1 | package android.content.res; 2 | 3 | import android.app.AndroidAppHelper; 4 | import android.util.DisplayMetrics; 5 | 6 | import de.robv.android.xposed.IXposedHookInitPackageResources; 7 | import de.robv.android.xposed.IXposedHookZygoteInit; 8 | import de.robv.android.xposed.IXposedHookZygoteInit.StartupParam; 9 | import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam; 10 | import dev.rikka.tools.refine.Refine; 11 | 12 | /** 13 | * Provides access to resources from a certain path (usually the module's own path). 14 | */ 15 | public class XModuleResources extends Resources { 16 | private XModuleResources(AssetManager assets, DisplayMetrics metrics, Configuration config) { 17 | super(assets, metrics, config); 18 | } 19 | 20 | /** 21 | * Creates a new instance. 22 | * 23 | *

This is usually called with {@link StartupParam#modulePath} from 24 | * {@link IXposedHookZygoteInit#initZygote} and {@link InitPackageResourcesParam#res} from 25 | * {@link IXposedHookInitPackageResources#handleInitPackageResources} (or {@code null} for 26 | * system-wide replacements). 27 | * 28 | * @param path The path to the APK from which the resources should be loaded. 29 | * @param origRes The resources object from which settings like the display metrics and the 30 | * configuration should be copied. May be {@code null}. 31 | */ 32 | public static XModuleResources createInstance(String path, XResources origRes) { 33 | if (path == null) 34 | throw new IllegalArgumentException("path must not be null"); 35 | 36 | AssetManager assets = new AssetManager(); 37 | Refine.unsafeCast(assets).addAssetPath(path); 38 | 39 | XModuleResources res; 40 | if (origRes != null) 41 | res = new XModuleResources(assets, origRes.getDisplayMetrics(), origRes.getConfiguration()); 42 | else 43 | res = new XModuleResources(assets, null, null); 44 | 45 | AndroidAppHelper.addActiveResource(path, res); 46 | return res; 47 | } 48 | 49 | /** 50 | * Creates an {@link XResForwarder} instance that forwards requests to {@code id} in this resource. 51 | */ 52 | public XResForwarder fwd(int id) { 53 | return new XResForwarder(this, id); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(Dreamland) 2 | cmake_minimum_required(VERSION 3.4.1) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 6 | 7 | set(C_FLAGS "-Wall -Wextra -Wno-unused-parameter -fvisibility-inlines-hidden -fno-exceptions -fno-rtti -flto=thin -Wno-builtin-macro-redefined -D__FILE__=__FILE_NAME__") 8 | set(LINKER_FLAGS "-fuse-ld=lld -flto=thin -ffixed-x18 -Wl,--hash-style=both -Wl,--unresolved-symbols=ignore-all") 9 | 10 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${C_FLAGS}") 11 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${C_FLAGS}") 12 | 13 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${LINKER_FLAGS}") 14 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} ${LINKER_FLAGS}") 15 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LINKER_FLAGS}") 16 | 17 | add_definitions(-DRIRU_NEW_MODULE_API_VERSION=${RIRU_NEW_MODULE_API_VERSION}) 18 | add_definitions(-DRIRU_MODULE_VERSION_NAME="${RIRU_MODULE_VERSION_NAME}") 19 | add_definitions(-DDREAMLAND_VERSION_CODE=${DREAMLAND_VERSION_CODE}) 20 | 21 | add_library(riru_dreamland 22 | SHARED 23 | utils/well_known_classes.cpp 24 | dreamland/flavor.cpp 25 | dreamland/riru_flavor.cpp 26 | dreamland/zygisk_flavor.cpp 27 | dreamland/dreamland.cpp 28 | dreamland/android.cpp 29 | dreamland/resources_hook.cpp 30 | dreamland/native_hook.cpp 31 | dreamland/binder.cpp 32 | dreamland/dex_loader.cpp) 33 | 34 | find_library(log-lib log) 35 | find_package(cxx REQUIRED CONFIG) 36 | find_package(dobby REQUIRED CONFIG) 37 | 38 | add_library(pine STATIC IMPORTED) 39 | add_library(pine-enhances STATIC IMPORTED) 40 | 41 | get_filename_component(current_source_dir ${CMAKE_CURRENT_SOURCE_DIR} ABSOLUTE) 42 | 43 | set(external_dir "${current_source_dir}/../../../../external") 44 | get_filename_component(export_dir ${external_dir} ABSOLUTE) 45 | 46 | set_target_properties(pine PROPERTIES IMPORTED_LOCATION ${external_dir}/pine/${ANDROID_ABI}/libpine.a) 47 | set_target_properties(pine-enhances PROPERTIES IMPORTED_LOCATION ${external_dir}/pine-enhances/${ANDROID_ABI}/libpine-enhances.a) 48 | 49 | target_link_libraries(riru_dreamland ${log-lib} cxx::cxx dobby::dobby pine pine-enhances) 50 | 51 | ENABLE_LANGUAGE(ASM) 52 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/core/AppManager.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.core; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import top.canyie.dreamland.utils.collections.ConcurrentHashSet; 6 | 7 | import java.util.Set; 8 | import java.util.StringTokenizer; 9 | 10 | /** 11 | * @author canyie 12 | */ 13 | public final class AppManager extends BaseManager> { 14 | public AppManager() { 15 | super("apps.list"); 16 | } 17 | 18 | public boolean isEnabled(String packageName) { 19 | return getRealObject().contains(packageName); 20 | } 21 | 22 | public void setEnabled(String packageName, boolean enabled) { 23 | Set set = getRealObject(); 24 | boolean changed = enabled ? set.add(packageName) : set.remove(packageName); 25 | if (changed) notifyDataChanged(); 26 | } 27 | 28 | public Set getAllEnabled() { 29 | // Note: From the principle of encapsulation, the real object should not be returned directly, 30 | // but this is not a public API 31 | // and we know that no write operation will be performed on the return value. 32 | return getRealObject(); 33 | } 34 | 35 | @NonNull @Override protected String serialize(ConcurrentHashSet set) { 36 | StringBuilder sb = new StringBuilder(); 37 | for (String packageName : set) { 38 | if (packageName == null || (packageName = packageName.trim()).isEmpty()) continue; 39 | sb.append(packageName).append('\n'); 40 | } 41 | return sb.toString(); 42 | } 43 | 44 | @Override protected ConcurrentHashSet deserialize(String str) { 45 | StringTokenizer st = new StringTokenizer(str, "\n"); 46 | ConcurrentHashSet set = new ConcurrentHashSet<>(Math.max((int) (st.countTokens() / .75f) + 1, 16)); 47 | while (st.hasMoreTokens()) { 48 | String packageName = st.nextToken(); 49 | if (packageName == null || (packageName = packageName.trim()).isEmpty()) continue; 50 | set.add(packageName); 51 | } 52 | return set; 53 | } 54 | 55 | @NonNull @Override protected ConcurrentHashSet createEmpty() { 56 | return new ConcurrentHashSet<>(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/collections/ConcurrentHashSet.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils.collections; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import java.util.AbstractSet; 7 | import java.util.Collection; 8 | import java.util.Iterator; 9 | import java.util.concurrent.ConcurrentHashMap; 10 | 11 | /** 12 | * @author canyie 13 | */ 14 | public final class ConcurrentHashSet extends AbstractSet implements Cloneable { 15 | private static final Object PRESENT = new Object(); 16 | private transient ConcurrentHashMap map; 17 | 18 | public ConcurrentHashSet() { 19 | map = new ConcurrentHashMap<>(); 20 | } 21 | 22 | public ConcurrentHashSet(Collection c) { 23 | map = new ConcurrentHashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); 24 | addAll(c); 25 | } 26 | 27 | public ConcurrentHashSet(int initialCapacity) { 28 | map = new ConcurrentHashMap<>(initialCapacity); 29 | } 30 | 31 | public ConcurrentHashSet(int initialCapacity, float loadFactor) { 32 | map = new ConcurrentHashMap<>(initialCapacity, loadFactor); 33 | } 34 | 35 | @Override public boolean add(@NonNull E e) { 36 | return map.put(e, PRESENT) == null; 37 | } 38 | 39 | @Override public boolean remove(@Nullable Object o) { 40 | if (o == null) return false; // ConcurrentHashMap does not allow null. 41 | return map.remove(o) == PRESENT; 42 | } 43 | 44 | @Override public void clear() { 45 | map.clear(); 46 | } 47 | 48 | @SuppressWarnings("SuspiciousMethodCalls") @Override public boolean contains(@Nullable Object o) { 49 | if (o == null) return false; // ConcurrentHashMap does not allow null. 50 | return map.containsKey(o); 51 | } 52 | 53 | @NonNull @Override public Iterator iterator() { 54 | return map.keySet().iterator(); 55 | } 56 | 57 | @Override public int size() { 58 | return map.size(); 59 | } 60 | 61 | @SuppressWarnings("unchecked") @NonNull @Override protected Object clone() { 62 | try { 63 | ConcurrentHashSet newSet = (ConcurrentHashSet) super.clone(); 64 | newSet.map = new ConcurrentHashMap<>(map); 65 | return newSet; 66 | } catch (CloneNotSupportedException e) { 67 | throw new AssertionError(e); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | -keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keepattributes RuntimeVisibleAnnotations 24 | 25 | # Gson uses generic type information stored in a class file when working with fields. Proguard 26 | # removes such information by default, so configure it to keep all of it. 27 | -keepattributes Signature 28 | 29 | # For using GSON @Expose annotation 30 | -keepattributes *Annotation* 31 | 32 | # Gson specific classes 33 | -dontwarn sun.misc.** 34 | #-keep class com.google.gson.stream.** { *; } 35 | 36 | # Application classes that will be serialized/deserialized over Gson 37 | -keep class com.google.gson.examples.android.model.** { ; } 38 | 39 | # Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory, 40 | # JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter) 41 | -keep class * extends com.google.gson.TypeAdapter 42 | -keep class * implements com.google.gson.TypeAdapterFactory 43 | -keep class * implements com.google.gson.JsonSerializer 44 | -keep class * implements com.google.gson.JsonDeserializer 45 | 46 | # Prevent R8 from leaving Data object members always null 47 | -keepclassmembers,allowobfuscation class * { 48 | @com.google.gson.annotations.SerializedName ; 49 | } 50 | 51 | # Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher. 52 | -keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken 53 | -keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken 54 | 55 | -keep class top.canyie.pine.enhances.PineEnhances { 56 | private static void onClassInit(long); 57 | } 58 | 59 | # Xposed APIs 60 | -keep class de.robv.android.xposed.** { *; } 61 | -keep class android.** { *; } 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/dex_loader.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2021/2/2. 3 | // 4 | 5 | #include "dex_loader.h" 6 | #include "../utils/well_known_classes.h" 7 | #include "android.h" 8 | #include "../utils/scoped_local_ref.h" 9 | 10 | using namespace dreamland; 11 | 12 | jclass DexLoader::PathClassLoader = nullptr; 13 | jmethodID DexLoader::PathClassLoader_init = nullptr; 14 | jclass DexLoader::InMemoryDexClassLoader = nullptr; 15 | jmethodID DexLoader::InMemoryDexClassLoader_init = nullptr; 16 | 17 | void DexLoader::Prepare(JNIEnv* env) { 18 | if (Android::version < Android::kO) { 19 | PathClassLoader = WellKnownClasses::CacheClass(env, "dalvik/system/PathClassLoader"); 20 | PathClassLoader_init = WellKnownClasses::CacheMethod(env, PathClassLoader, "", 21 | "(Ljava/lang/String;Ljava/lang/ClassLoader;)V", false); 22 | } else { 23 | InMemoryDexClassLoader = WellKnownClasses::CacheClass(env, "dalvik/system/InMemoryDexClassLoader"); 24 | InMemoryDexClassLoader_init = WellKnownClasses::CacheMethod(env, InMemoryDexClassLoader, "", 25 | "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V", false); 26 | } 27 | } 28 | 29 | jobject DexLoader::FromMemory(JNIEnv* env, char* dex, const size_t size) { 30 | ScopedLocalRef system_class_loader(env, env->CallStaticObjectMethod( 31 | WellKnownClasses::java_lang_ClassLoader, 32 | WellKnownClasses::java_lang_ClassLoader_getSystemClassLoader)); 33 | ScopedLocalRef buffer(env, env->NewDirectByteBuffer(dex, size)); 34 | jobject loader = env->NewObject(InMemoryDexClassLoader, InMemoryDexClassLoader_init, 35 | buffer.Get(), system_class_loader.Get()); 36 | if (UNLIKELY(env->ExceptionCheck())) { 37 | LOGE("Failed to load dex data"); 38 | env->ExceptionDescribe(); 39 | env->ExceptionClear(); 40 | return nullptr; 41 | } 42 | return loader; 43 | } 44 | 45 | jobject DexLoader::FromFile(JNIEnv* env, const char* path) { 46 | ScopedLocalRef dex_path(env, path); 47 | ScopedLocalRef system_class_loader(env, env->CallStaticObjectMethod( 48 | WellKnownClasses::java_lang_ClassLoader, 49 | WellKnownClasses::java_lang_ClassLoader_getSystemClassLoader)); 50 | jobject loader = env->NewObject(PathClassLoader, PathClassLoader_init, dex_path.Get(), system_class_loader.Get()); 51 | if (UNLIKELY(env->ExceptionCheck())) { 52 | LOGE("Failed to load dex %s", path); 53 | env->ExceptionDescribe(); 54 | env->ExceptionClear(); 55 | return nullptr; 56 | } 57 | return loader; 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/IOUtils.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils; 2 | 3 | import android.system.ErrnoException; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.annotation.Nullable; 8 | 9 | import java.io.BufferedReader; 10 | import java.io.BufferedWriter; 11 | import java.io.Closeable; 12 | import java.io.File; 13 | import java.io.FileReader; 14 | import java.io.FileWriter; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.InputStreamReader; 18 | import java.io.Reader; 19 | 20 | /** 21 | * Created by canyie on 2019/11/24. 22 | */ 23 | public final class IOUtils { 24 | private static final String TAG = "IOUtils"; 25 | private IOUtils() {} 26 | 27 | public static String readAllString(@NonNull File file) throws IOException { 28 | return readAllString(new FileReader(file)); 29 | } 30 | 31 | public static String readAllString(@NonNull InputStream input) throws IOException { 32 | return readAllString(new InputStreamReader(input)); 33 | } 34 | 35 | public static String readAllString(@NonNull Reader input) throws IOException { 36 | BufferedReader br = new BufferedReader(input); 37 | try { 38 | StringBuilder sb = new StringBuilder(); 39 | String line; 40 | while((line = br.readLine()) != null) { 41 | sb.append(line).append('\n'); 42 | } 43 | return sb.toString(); 44 | } finally { 45 | closeQuietly(br); 46 | } 47 | } 48 | 49 | public static void writeToFile(File file, String content) throws IOException { 50 | FileWriter fw = new FileWriter(file); 51 | try { 52 | fw.write(content); 53 | } finally { 54 | closeQuietly(fw); 55 | } 56 | } 57 | 58 | public static void closeQuietly(@Nullable Closeable closeable) { 59 | if(closeable != null) { 60 | try { 61 | closeable.close(); 62 | } catch(IOException e) { 63 | Log.e(TAG, "Error while closing Closeable " + closeable, e); 64 | } 65 | } 66 | } 67 | 68 | public static File ensureDirectoryExisting(@NonNull File directory) { 69 | if (!(directory.exists() || directory.mkdirs() || directory.exists())) { 70 | throw new IllegalStateException("Can't create directory: " + directory.getAbsolutePath()); 71 | } 72 | return directory; 73 | } 74 | 75 | public static int getErrno(IOException e) { 76 | for (Throwable cause = e;cause != null;cause = cause.getCause()) { 77 | if (cause instanceof ErrnoException) { 78 | return ((ErrnoException) cause).errno; 79 | } 80 | } 81 | return 0; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/ipc/ModuleInfo.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.ipc; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | import androidx.annotation.Keep; 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | import com.google.gson.annotations.SerializedName; 11 | 12 | import java.util.Collections; 13 | import java.util.HashSet; 14 | import java.util.Set; 15 | 16 | /** 17 | * Created by canyie on 2019/11/12. 18 | */ 19 | @Keep public final class ModuleInfo implements Parcelable { 20 | // public String name; 21 | public String path; 22 | public String nativePath; 23 | public boolean enabled = true; 24 | 25 | /** 26 | * This module has been enabled for the applications contained in the collection. 27 | * null: module activation scope not enabled for this module. 28 | */ 29 | @Nullable @SerializedName(value = "scope", alternate = "enabledFor") private Set scope; 30 | 31 | public ModuleInfo() { 32 | } 33 | 34 | public ModuleInfo(String path, String nativePath) { 35 | this.path = path; 36 | this.nativePath = nativePath; 37 | } 38 | 39 | public static final Creator CREATOR = new Creator<>() { 40 | @Override 41 | public ModuleInfo createFromParcel(Parcel in) { 42 | String path = in.readString(); 43 | String nativePath = in.readString(); 44 | return new ModuleInfo(path, nativePath); 45 | } 46 | 47 | @Override 48 | public ModuleInfo[] newArray(int size) { 49 | return new ModuleInfo[size]; 50 | } 51 | }; 52 | 53 | public boolean isEnabledFor(String packageName) { 54 | return scope == null || scope.contains(packageName); 55 | } 56 | 57 | public String[] getScope() { 58 | if (scope == null) return null; 59 | return scope.toArray(new String[scope.size()]); 60 | } 61 | 62 | public void setScope(String[] packages) { 63 | if (packages == null) { 64 | // Disable module activation scope 65 | scope = null; 66 | return; 67 | } 68 | if (scope == null) 69 | scope = new HashSet<>(4); 70 | else 71 | scope.clear(); 72 | Collections.addAll(scope, packages); 73 | } 74 | 75 | @Override public int describeContents() { 76 | return 0; 77 | } 78 | 79 | @Override public void writeToParcel(@NonNull Parcel dest, int flags) { 80 | dest.writeString(path); 81 | dest.writeString(nativePath); 82 | } 83 | 84 | @NonNull @Override public String toString() { 85 | return "ModuleInfo{path=" + path + ", nativeDir=" + nativePath + ", enabled=" + enabled + "}"; 86 | } 87 | 88 | // public ModuleInfo(String name, String path) { 89 | // this.name = name; 90 | // this.path = path; 91 | // } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/callbacks/XC_LayoutInflated.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.callbacks; 2 | 3 | import android.content.res.XResources; 4 | import android.content.res.XResources.ResourceNames; 5 | import android.view.View; 6 | 7 | import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet; 8 | 9 | /** 10 | * Callback for hooking layouts. Such callbacks can be passed to {@link XResources#hookLayout} 11 | * and its variants. 12 | */ 13 | public abstract class XC_LayoutInflated extends XCallback { 14 | /** 15 | * Creates a new callback with default priority. 16 | */ 17 | @SuppressWarnings("deprecation") 18 | public XC_LayoutInflated() { 19 | super(); 20 | } 21 | 22 | /** 23 | * Creates a new callback with a specific priority. 24 | * 25 | * @param priority See {@link XCallback#priority}. 26 | */ 27 | public XC_LayoutInflated(int priority) { 28 | super(priority); 29 | } 30 | 31 | /** 32 | * Wraps information about the inflated layout. 33 | */ 34 | public static final class LayoutInflatedParam extends Param { 35 | /** @hide */ 36 | public LayoutInflatedParam(CopyOnWriteSortedSet callbacks) { 37 | super(callbacks); 38 | } 39 | 40 | /** The view that has been created from the layout. */ 41 | public View view; 42 | 43 | /** Container with the ID and name of the underlying resource. */ 44 | public ResourceNames resNames; 45 | 46 | /** Directory from which the layout was actually loaded (e.g. "layout-sw600dp"). */ 47 | public String variant; 48 | 49 | /** Resources containing the layout. */ 50 | public XResources res; 51 | } 52 | 53 | /** @hide */ 54 | @Override 55 | protected void call(Param param) throws Throwable { 56 | if (param instanceof LayoutInflatedParam) 57 | handleLayoutInflated((LayoutInflatedParam) param); 58 | } 59 | 60 | /** 61 | * This method is called when the hooked layout has been inflated. 62 | * 63 | * @param liparam Information about the layout and the inflated view. 64 | * @throws Throwable Everything the callback throws is caught and logged. 65 | */ 66 | public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable; 67 | 68 | /** 69 | * An object with which the callback can be removed. 70 | */ 71 | public class Unhook implements IXUnhook { 72 | private final String resDir; 73 | private final int id; 74 | 75 | /** @hide */ 76 | public Unhook(String resDir, int id) { 77 | this.resDir = resDir; 78 | this.id = id; 79 | } 80 | 81 | /** 82 | * Returns the resource ID of the hooked layout. 83 | */ 84 | public int getId() { 85 | return id; 86 | } 87 | 88 | @Override 89 | public XC_LayoutInflated getCallback() { 90 | return XC_LayoutInflated.this; 91 | } 92 | 93 | @Override 94 | public void unhook() { 95 | XResources.unhookLayout(resDir, id, XC_LayoutInflated.this); 96 | } 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/dreamland.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2019/11/12. 3 | // 4 | 5 | #ifndef DREAMLAND_DREAMLAND_H 6 | #define DREAMLAND_DREAMLAND_H 7 | 8 | #include 9 | #include 10 | #include "../utils/log.h" 11 | #include "../utils/macros.h" 12 | #include "android.h" 13 | 14 | namespace dreamland { 15 | class Dreamland { 16 | public: 17 | static constexpr const char* VERSION_NAME = RIRU_MODULE_VERSION_NAME; 18 | static constexpr int VERSION = DREAMLAND_VERSION_CODE; 19 | 20 | static bool ShouldDisable(); 21 | 22 | static void Prepare(bool preload); 23 | 24 | static bool PrepareJava(JNIEnv* env, bool zygote); 25 | 26 | static Dreamland* GetInstance() { 27 | return instance; 28 | } 29 | 30 | static bool ShouldSkipUid(int uid) { 31 | // TODO: Get these uids through java world 32 | int app_id = uid % 100000; 33 | if (UNLIKELY(app_id >= 90000)) { 34 | // Isolated process 35 | return true; 36 | } 37 | if (UNLIKELY(app_id == 1037)) { 38 | // RELRO updater 39 | return true; 40 | } 41 | if (Android::version >= Android::kO) { 42 | int kWebViewZygoteUid = Android::version >= Android::kP ? 1053 : 1051; 43 | if (UNLIKELY(uid == kWebViewZygoteUid)) { 44 | // WebView zygote 45 | return true; 46 | } 47 | } 48 | return false; 49 | } 50 | 51 | static bool OnAppProcessStart(JNIEnv* env, bool start_system_server); 52 | 53 | static bool OnSystemServerStart(JNIEnv* env); 54 | 55 | JavaVM* GetJavaVM() { 56 | return java_vm; 57 | } 58 | 59 | JNIEnv* GetJNIEnv(); 60 | 61 | private: 62 | bool LoadDexDirectly(JNIEnv* env, bool zygote); 63 | 64 | bool PrepareJavaImpl(JNIEnv* env, bool preload); 65 | 66 | bool RegisterNatives(JNIEnv* env, jclass main_class, jobject class_loader); 67 | 68 | bool FindEntryMethods(JNIEnv* env, jclass main_class, bool app, bool system_server); 69 | 70 | bool EnsureDexLoaded(JNIEnv* env, bool app); 71 | 72 | bool LoadDexFromMemory(JNIEnv* env, bool app); 73 | 74 | static void PreloadDexData(); 75 | 76 | Dreamland(); 77 | 78 | ~Dreamland(); 79 | 80 | static Dreamland* instance; 81 | static std::vector* dex_data; 82 | JavaVM* java_vm; 83 | jclass java_main_class; 84 | jmethodID onSystemServerStart; 85 | jmethodID onAppProcessStart; 86 | 87 | static constexpr const char* kBaseDir = "/data/misc/dreamland/"; 88 | static constexpr const char* kCoreJarFile = "/data/misc/dreamland/dreamland.jar"; 89 | static constexpr const char* kDisableFile = "/data/misc/dreamland/disable"; 90 | DISALLOW_COPY_AND_ASSIGN(Dreamland); 91 | }; 92 | } 93 | 94 | #endif //DREAMLAND_DREAMLAND_H 95 | -------------------------------------------------------------------------------- /app/src/main/cpp/utils/well_known_classes.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2019/11/18. 3 | // 4 | #include "well_known_classes.h" 5 | 6 | using namespace dreamland; 7 | 8 | bool WellKnownClasses::inited = false; 9 | 10 | //jclass WellKnownClasses::java_lang_Object = nullptr; 11 | //jclass WellKnownClasses::java_lang_Class = nullptr; 12 | jclass WellKnownClasses::java_lang_ClassLoader = nullptr; 13 | jclass WellKnownClasses::java_lang_String = nullptr; 14 | //jclass WellKnownClasses::java_lang_Thread = nullptr; 15 | //jclass WellKnownClasses::dalvik_system_DexClassLoader = nullptr; 16 | 17 | jmethodID WellKnownClasses::java_lang_ClassLoader_loadClass = nullptr; 18 | jmethodID WellKnownClasses::java_lang_ClassLoader_getSystemClassLoader = nullptr; 19 | 20 | jclass WellKnownClasses::CacheClass(JNIEnv *env, const char *name) { 21 | jclass localClassRef = env->FindClass(name); 22 | CHECK_FOR_JNI(localClassRef != nullptr, "Didn't find class '%s'", name); 23 | jclass globalClassRef = reinterpret_cast (env->NewGlobalRef(localClassRef)); 24 | env->DeleteLocalRef(localClassRef); 25 | CHECK_FOR_JNI(globalClassRef != nullptr, "globalClassRef == nullptr; out of memory? "); 26 | return globalClassRef; 27 | } 28 | 29 | jmethodID WellKnownClasses::CacheMethod(JNIEnv *env, jclass klass, const char *name, 30 | const char *signature, bool is_static) { 31 | jmethodID method = is_static ? env->GetStaticMethodID(klass, name, signature) 32 | : env->GetMethodID(klass, name, signature); 33 | CHECK_FOR_JNI(method != nullptr, "No match method %s%s.", name, signature); 34 | return method; 35 | } 36 | 37 | void WellKnownClasses::Init(JNIEnv *env) { 38 | if (inited) return; 39 | //java_lang_Object = CacheClass(env, "java/lang/Object"); 40 | //java_lang_Class = CacheClass(env, "java/lang/Class"); 41 | java_lang_ClassLoader = CacheClass(env, "java/lang/ClassLoader"); 42 | java_lang_String = CacheClass(env, "java/lang/String"); 43 | //java_lang_Thread = CacheClass(env, "java/lang/Thread"); 44 | //dalvik_system_DexClassLoader = CacheClass(env, "dalvik/system/DexClassLoader"); 45 | 46 | java_lang_ClassLoader_loadClass = CacheMethod(env, java_lang_ClassLoader, "loadClass", 47 | "(Ljava/lang/String;)Ljava/lang/Class;", false); 48 | 49 | java_lang_ClassLoader_getSystemClassLoader = CacheMethod(env, java_lang_ClassLoader, 50 | "getSystemClassLoader", "()Ljava/lang/ClassLoader;", true); 51 | inited = true; 52 | } 53 | 54 | void WellKnownClasses::Clear(JNIEnv *env) { 55 | //ClearGlobalReference(env, &java_lang_Object); 56 | //ClearGlobalReference(env, &java_lang_Class); 57 | ClearGlobalReference(env, &java_lang_ClassLoader); 58 | ClearGlobalReference(env, &java_lang_String); 59 | //ClearGlobalReference(env, &java_lang_Thread); 60 | //ClearGlobalReference(env, &dalvik_system_DexClassLoader); 61 | 62 | java_lang_ClassLoader_loadClass = nullptr; 63 | java_lang_ClassLoader_getSystemClassLoader = nullptr; 64 | 65 | inited = false; 66 | } -------------------------------------------------------------------------------- /app/src/main/cpp/utils/jni_helper.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2019/11/24. 3 | // 4 | 5 | #ifndef DREAMLAND_JNI_HELPER_H 6 | #define DREAMLAND_JNI_HELPER_H 7 | 8 | #include 9 | #include "well_known_classes.h" 10 | 11 | namespace dreamland { 12 | class JNIHelper { 13 | public: 14 | static bool ExceptionCheck(JNIEnv *env) { 15 | if(env->ExceptionCheck()) { 16 | LOGE("JNI Exception: "); 17 | env->ExceptionDescribe(); 18 | env->ExceptionClear(); 19 | return true; 20 | } 21 | return false; 22 | } 23 | 24 | static bool ThrowException(JNIEnv *env, const char *className, const char *errMsg) { 25 | if(env->ExceptionCheck()) { 26 | LOGW("Ignoring JNI exception: "); 27 | env->ExceptionDescribe(); 28 | env->ExceptionClear(); 29 | } 30 | ScopedLocalRef exceptionClass(env, env->FindClass(className)); 31 | if(exceptionClass == nullptr) { 32 | return false; 33 | } 34 | return env->ThrowNew(exceptionClass.Get(), errMsg) == JNI_OK; 35 | } 36 | 37 | static bool RegisterNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) { 38 | ScopedLocalRef clazz(env, env->FindClass(className)); 39 | if(clazz == nullptr) { 40 | return false; 41 | } 42 | if(env->RegisterNatives(clazz.Get(), methods, numMethods) < 0) { 43 | LOGE("Failed to register native methods of class %s", className); 44 | return false; 45 | } 46 | return true; 47 | } 48 | 49 | static jclass FindClassFromClassLoader(JNIEnv* env, const char* name, jobject loader) { 50 | ScopedLocalRef name_ref(env, name); 51 | return static_cast(env->CallObjectMethod(loader, 52 | WellKnownClasses::java_lang_ClassLoader_loadClass, name_ref.Get())); 53 | } 54 | 55 | static void AssertPendingException(JNIEnv *env) { 56 | CHECK_FOR_JNI(env->ExceptionCheck(), "Assert pending exception failed"); 57 | } 58 | 59 | static void AssertAndClearPendingException(JNIEnv *env) { 60 | AssertPendingException(env); 61 | LOGE("Pending exception: "); 62 | env->ExceptionDescribe(); 63 | env->ExceptionClear(); 64 | } 65 | 66 | static bool GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature, bool is_static, jmethodID *out) { 67 | jmethodID method = is_static ? env->GetStaticMethodID(clazz, name, signature) : env->GetMethodID(clazz, name, signature); 68 | if (method == nullptr) { 69 | AssertPendingException(env); 70 | return false; 71 | } 72 | *out = method; 73 | return true; 74 | } 75 | 76 | private: 77 | DISALLOW_IMPLICIT_CONSTRUCTORS(JNIHelper); 78 | }; 79 | } 80 | 81 | #endif //DREAMLAND_JNI_HELPER_H 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dreamland [![Build Status](https://dev.azure.com/ssz33334930121/ssz3333493/_apis/build/status/canyie.Dreamland?branchName=master)](https://dev.azure.com/ssz33334930121/ssz3333493/_build/latest?definitionId=1&branchName=master) 2 | 3 | [中文版本](https://github.com/canyie/Dreamland/blob/master/README_CN.md) 4 | 5 | ## Introduction 6 | Dreamland is a Xposed framework implementation, you can use it to use Xposed modules. 7 | - Supports Android 5.0~**14** 8 | - Low invasiveness 9 | - For hooking third apps, only requires restart the target app, not the device 10 | - Strictly restrict modules, you can choose which apps need to load which modules 11 | 12 | **Note: Currently I do not have enough time and energy to maintain this project, so the development of this project will be inactive (but will not stop). I think other mature projects (like [LSPosed](https://github.com/LSPosed/LSPosed) and [TaiChi](https://taichi.cool/) ) will be good substitutes.** 13 | 14 | ## Warning 15 | **Dreamland needs to modify the system, installing Dreamland is dangerous; In any case, please back up your data yourself and be prepared for equipment damage, your use of Dreamland is at your own risk, we are not responsible for any of your losses.** 16 | 17 | ## Install 18 | 1. Install Magisk (the latest version is recommended). 19 | 2. Install [Riru](https://github.com/RikkaApps/Riru) or turn on Zygisk in Magisk app and reboot. 20 | 3. Download and install Dreamland in Magisk app. Installing from custom recovery is NO longer supported. 21 | 4. Install [Dreamland Manager](https://github.com/canyie/DreamlandManager/releases) 22 | 5. Reboot. (For Magisk < 21006, you have to reboot twice.) 23 | 24 | ## Download channel 25 | - Beta: Test version, released by the developer, download from our [GitHub Release Page](https://github.com/canyie/Dreamland/releases). 26 | - Canary: Test version, automatically build by CI, use of the version is at your own risk. Download from [Azure Pipelines](https://dev.azure.com/ssz33334930121/ssz3333493/_build/latest?definitionId=1&branchName=master). 27 | 28 | ## Known issues 29 | - Thanox not working. Do NOT use Thanox otherwise your device will go into bootloop. 30 | - [New XSharedPreferences](https://github.com/LSPosed/LSPosed/wiki/New-XSharedPreferences) is not implemented in Dreamland, some modules that requires this feature will not work. We are planing a new API named "XRemotePreferences", please go to [this page](https://github.com/libxposed/XposedService/issues/1) to learn more and talk to us. 31 | 32 | ## Discussion 33 | - [QQ Group: 949888394](https://shang.qq.com/wpa/qunwpa?idkey=25549719b948d2aaeb9e579955e39d71768111844b370fcb824d43b9b20e1c04) 34 | - [Telegram Group: @DreamlandFramework](https://t.me/DreamlandFramework) 35 | 36 | ## Credits 37 | - [Xposed](https://github.com/rovo89/Xposed): the original Xposed framework 38 | - [EdXposed](https://github.com/ElderDrivers/EdXposed) 39 | - [LSPosed](https://github.com/LSPosed/LSPosed): another modern Xposed framework 40 | - [Magisk](https://github.com/topjohnwu/Magisk): makes all these possible 41 | - [Riru](https://github.com/RikkaApps/Riru): provides a way to inject codes into zygote process 42 | - [Pine](https://github.com/canyie/pine): ART hooking library 43 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/GetClassLoaderHook.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland; 2 | 3 | import android.app.LoadedApk; 4 | import android.content.pm.ApplicationInfo; 5 | import android.os.RemoteException; 6 | import android.util.Log; 7 | 8 | import java.lang.reflect.Method; 9 | import java.util.List; 10 | 11 | import de.robv.android.xposed.XposedHelpers; 12 | import top.canyie.dreamland.core.Dreamland; 13 | import top.canyie.dreamland.ipc.IDreamlandManager; 14 | import top.canyie.dreamland.ipc.ModuleInfo; 15 | import top.canyie.dreamland.utils.reflect.Reflection; 16 | import top.canyie.pine.Pine; 17 | import top.canyie.pine.callback.MethodHook; 18 | 19 | /** 20 | * @author canyie 21 | */ 22 | class GetClassLoaderHook extends MethodHook { 23 | private static final Method target; 24 | private static final boolean hookGet; 25 | private IDreamlandManager dm; 26 | private LoadedApk loadedApk; 27 | private String packageName; 28 | private String processName; 29 | private ApplicationInfo appInfo; 30 | private boolean isFirstApp; 31 | private ModuleInfo[] modules; 32 | private MethodHook.Unhook unhook; 33 | 34 | static { 35 | Method create = Reflection.findMethod(LoadedApk.class, "createOrUpdateClassLoaderLocked", List.class); 36 | hookGet = create == null; 37 | target = hookGet ? Reflection.getMethod(LoadedApk.class, "getClassLoader") : create; 38 | } 39 | 40 | private GetClassLoaderHook() { 41 | } 42 | 43 | public static void install(IDreamlandManager dm, LoadedApk loadedApk, String packageName, 44 | String processName, ApplicationInfo appInfo, boolean isFirstApp) { 45 | ModuleInfo[] modules; 46 | 47 | try { 48 | modules = dm.getEnabledModulesFor(packageName); 49 | } catch (RemoteException e) { 50 | Log.e(Dreamland.TAG, "Failure from remote dreamland service", e); 51 | return; 52 | } 53 | 54 | if (modules == null || modules.length == 0) { 55 | Log.i(Dreamland.TAG, "No module needs to hook into package " + packageName); 56 | return; 57 | } 58 | 59 | GetClassLoaderHook hook = new GetClassLoaderHook(); 60 | hook.dm = dm; 61 | hook.loadedApk = loadedApk; 62 | hook.packageName = packageName; 63 | hook.processName = processName; 64 | hook.appInfo = appInfo; 65 | hook.isFirstApp = isFirstApp; 66 | hook.modules = modules; 67 | try { 68 | hook.unhook = Pine.hook(target, hook); 69 | } catch (Throwable e) { 70 | Log.e(Dreamland.TAG, "hook getClassLoader failed", e); 71 | } 72 | } 73 | 74 | @Override public void afterCall(Pine.CallFrame callFrame) { 75 | LoadedApk loadedApk = (LoadedApk) callFrame.thisObject; 76 | if (loadedApk != this.loadedApk) return; 77 | 78 | try { 79 | ClassLoader classLoader = (ClassLoader) (hookGet 80 | ? callFrame.getResult() 81 | : XposedHelpers.getObjectField(loadedApk, "mClassLoader")); 82 | Dreamland.packageReady(dm, packageName, processName, appInfo, classLoader, isFirstApp, Main.mainZygote, modules); 83 | } finally { 84 | unhook.unhook(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/content/res/TypedArrayHidden.java: -------------------------------------------------------------------------------- 1 | package android.content.res; 2 | 3 | import android.graphics.Typeface; 4 | import android.graphics.drawable.Drawable; 5 | import android.util.TypedValue; 6 | 7 | import dev.rikka.tools.refine.RefineAs; 8 | 9 | @RefineAs(TypedArray.class) 10 | public class TypedArrayHidden { 11 | /** Only for API stubs creation, DO NOT USE! */ 12 | public TypedArrayHidden() { 13 | throw new UnsupportedOperationException("Stub!"); 14 | } 15 | 16 | protected TypedArrayHidden(Resources resources) { 17 | throw new UnsupportedOperationException("Stub!"); 18 | } 19 | 20 | protected TypedArrayHidden(Resources resources, int[] data, int[] indices, int len) { 21 | throw new UnsupportedOperationException("Stub!"); 22 | } 23 | 24 | public boolean getBoolean(int index, boolean defValue) { 25 | throw new UnsupportedOperationException("Stub!"); 26 | } 27 | 28 | public int getColor(int index, int defValue) { 29 | throw new UnsupportedOperationException("Stub!"); 30 | } 31 | 32 | public ColorStateList getColorStateList(int index) { 33 | throw new UnsupportedOperationException("Stub!"); 34 | } 35 | 36 | public float getDimension(int index, float defValue) { 37 | throw new UnsupportedOperationException("Stub!"); 38 | } 39 | 40 | public int getDimensionPixelOffset(int index, int defValue) { 41 | throw new UnsupportedOperationException("Stub!"); 42 | } 43 | 44 | public int getDimensionPixelSize(int index, int defValue) { 45 | throw new UnsupportedOperationException("Stub!"); 46 | } 47 | 48 | public Drawable getDrawable(int index) { 49 | throw new UnsupportedOperationException("Stub!"); 50 | } 51 | 52 | public float getFloat(int index, float defValue) { 53 | throw new UnsupportedOperationException("Stub!"); 54 | } 55 | 56 | public float getFraction(int index, int base, int pbase, float defValue) { 57 | throw new UnsupportedOperationException("Stub!"); 58 | } 59 | 60 | public int getInt(int index, int defValue) { 61 | throw new UnsupportedOperationException("Stub!"); 62 | } 63 | 64 | public int getInteger(int index, int defValue) { 65 | throw new UnsupportedOperationException("Stub!"); 66 | } 67 | 68 | public int getLayoutDimension(int index, int defValue) { 69 | throw new UnsupportedOperationException("Stub!"); 70 | } 71 | 72 | public int getLayoutDimension(int index, String name) { 73 | throw new UnsupportedOperationException("Stub!"); 74 | } 75 | 76 | public int getResourceId(int index, int defValue) { 77 | throw new UnsupportedOperationException("Stub!"); 78 | } 79 | 80 | public Resources getResources() { 81 | throw new UnsupportedOperationException("Stub!"); 82 | } 83 | 84 | public String getString(int index) { 85 | throw new UnsupportedOperationException("Stub!"); 86 | } 87 | 88 | public CharSequence getText(int index) { 89 | throw new UnsupportedOperationException("Stub!"); 90 | } 91 | 92 | public CharSequence[] getTextArray(int index) { 93 | throw new UnsupportedOperationException("Stub!"); 94 | } 95 | 96 | public Typeface getFont(int index) { 97 | throw new UnsupportedOperationException("Stub!"); 98 | } 99 | 100 | public boolean getValue(int index, TypedValue outValue) { 101 | throw new UnsupportedOperationException("Stub!"); 102 | } 103 | 104 | public TypedValue peekValue(int index) { 105 | throw new UnsupportedOperationException("Stub!"); 106 | } 107 | 108 | public void recycle() { 109 | throw new UnsupportedOperationException("Stub!"); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/ipc/BinderServiceProxy.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.ipc; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | import android.os.Parcel; 7 | import android.os.RemoteException; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | 12 | import java.io.FileDescriptor; 13 | 14 | /** 15 | * Proxy for binder service. 16 | * Note: For ServiceManager.addService, service must be a Binder object 17 | * See function ibinderForJavaObject (in android_util_Binder.cpp) 18 | */ 19 | public class BinderServiceProxy extends Binder { 20 | private static final int GET_BINDER_TRANSACTION = ('_'<<24)|('D'<<16)|('M'<<8)|'S'; 21 | private Binder base; 22 | private String descriptor; 23 | private IBinder service; 24 | private CallerVerifier verifier; 25 | 26 | public static IBinder getBinderFrom(IBinder service, String descriptor) throws RemoteException { 27 | Parcel data = Parcel.obtain(); 28 | Parcel reply = Parcel.obtain(); 29 | try { 30 | data.writeInterfaceToken(descriptor); 31 | boolean success = service.transact(GET_BINDER_TRANSACTION, data, reply, 0); 32 | reply.readException(); 33 | if (!success) { 34 | // Unknown transaction => remote service doesn't handle it, ignore this process. 35 | return null; 36 | } 37 | return reply.readStrongBinder(); 38 | } finally { 39 | reply.recycle(); 40 | data.recycle(); 41 | } 42 | } 43 | 44 | public BinderServiceProxy(Binder base, String descriptor, IBinder service, CallerVerifier verifier) { 45 | this.base = base; 46 | this.descriptor = descriptor; 47 | this.service = service; 48 | this.verifier = verifier; 49 | } 50 | 51 | @Nullable @Override public String getInterfaceDescriptor() { 52 | return base.getInterfaceDescriptor(); 53 | } 54 | 55 | @Override public boolean pingBinder() { 56 | return base.pingBinder(); 57 | } 58 | 59 | @Override public boolean isBinderAlive() { 60 | return base.isBinderAlive(); 61 | } 62 | 63 | @Nullable @Override public IInterface queryLocalInterface(@NonNull String descriptor) { 64 | return base.queryLocalInterface(descriptor); 65 | } 66 | 67 | @Override public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) { 68 | base.dump(fd, args); 69 | } 70 | 71 | @Override public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) { 72 | base.dumpAsync(fd, args); 73 | } 74 | 75 | @Override protected boolean onTransact(int code, @NonNull Parcel data, 76 | @Nullable Parcel reply, int flags) throws RemoteException { 77 | if (code == GET_BINDER_TRANSACTION && verifier.canAccessService()) { 78 | assert reply != null; 79 | data.enforceInterface(descriptor); 80 | reply.writeNoException(); 81 | reply.writeStrongBinder(service); 82 | return true; 83 | } 84 | return base.transact(code, data, reply, flags); 85 | } 86 | 87 | @Override public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { 88 | base.linkToDeath(recipient, flags); 89 | } 90 | 91 | @Override public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { 92 | return base.unlinkToDeath(recipient, flags); 93 | } 94 | 95 | @FunctionalInterface public interface CallerVerifier { 96 | boolean canAccessService(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/src/main/cpp/riru.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2020/12/6. 3 | // 4 | 5 | #ifndef DREAMLAND_RIRU_H 6 | #define DREAMLAND_RIRU_H 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #define EXPORT extern "C" __attribute__ ((visibility ("default"))) __attribute__((used)) 13 | 14 | typedef void(onModuleLoaded_v9)(); 15 | 16 | typedef int(shouldSkipUid_v9)(int uid); 17 | 18 | typedef void(nativeForkAndSpecializePre_v9)( 19 | JNIEnv* env, jclass cls, jint* uid, jint* gid, jintArray* gids, jint* runtimeFlags, 20 | jobjectArray* rlimits, jint* mountExternal, jstring* seInfo, jstring* niceName, 21 | jintArray* fdsToClose, jintArray* fdsToIgnore, jboolean* is_child_zygote, 22 | jstring* instructionSet, jstring* appDataDir, jboolean* isTopApp, 23 | jobjectArray* pkgDataInfoList, 24 | jobjectArray* whitelistedDataInfoList, jboolean* bindMountAppDataDirs, 25 | jboolean* bindMountAppStorageDirs); 26 | 27 | typedef void(nativeForkAndSpecializePost_v9)(JNIEnv* env, jclass cls, jint res); 28 | 29 | typedef void(nativeForkSystemServerPre_v9)( 30 | JNIEnv* env, jclass cls, uid_t* uid, gid_t* gid, jintArray* gids, jint* runtimeFlags, 31 | jobjectArray* rlimits, jlong* permittedCapabilities, jlong* effectiveCapabilities); 32 | 33 | typedef void(nativeForkSystemServerPost_v9)(JNIEnv* env, jclass cls, jint res); 34 | 35 | typedef void(nativeSpecializeAppProcessPre_v9)( 36 | JNIEnv* env, jclass cls, jint* uid, jint* gid, jintArray* gids, jint* runtimeFlags, 37 | jobjectArray* rlimits, jint* mountExternal, jstring* seInfo, jstring* niceName, 38 | jboolean* startChildZygote, jstring* instructionSet, jstring* appDataDir, 39 | jboolean* isTopApp, jobjectArray* pkgDataInfoList, jobjectArray* whitelistedDataInfoList, 40 | jboolean* bindMountAppDataDirs, jboolean* bindMountAppStorageDirs); 41 | 42 | typedef void(nativeSpecializeAppProcessPost_v9)(JNIEnv* env, jclass cls); 43 | 44 | typedef struct { 45 | int supportHide; 46 | int version; 47 | const char* versionName; 48 | onModuleLoaded_v9* onModuleLoaded; 49 | shouldSkipUid_v9* shouldSkipUid; 50 | nativeForkAndSpecializePre_v9* forkAndSpecializePre; 51 | nativeForkAndSpecializePost_v9* forkAndSpecializePost; 52 | nativeForkSystemServerPre_v9* forkSystemServerPre; 53 | nativeForkSystemServerPost_v9* forkSystemServerPost; 54 | nativeSpecializeAppProcessPre_v9* specializeAppProcessPre; 55 | nativeSpecializeAppProcessPost_v9* specializeAppProcessPost; 56 | } RiruModuleInfoV9; 57 | 58 | typedef RiruModuleInfoV9 RiruModuleInfoV10; 59 | 60 | typedef struct { 61 | int supportHide; 62 | int version; 63 | const char *versionName; 64 | onModuleLoaded_v9 *onModuleLoaded; 65 | shouldSkipUid_v9 *shouldSkipUid; // Actually unused in Riru V25+ 66 | nativeForkAndSpecializePre_v9 *forkAndSpecializePre; 67 | nativeForkAndSpecializePost_v9 *forkAndSpecializePost; 68 | nativeForkSystemServerPre_v9 *forkSystemServerPre; 69 | nativeForkSystemServerPost_v9 *forkSystemServerPost; 70 | nativeSpecializeAppProcessPre_v9 *specializeAppProcessPre; 71 | nativeSpecializeAppProcessPost_v9 *specializeAppProcessPost; 72 | } RiruModuleInfo; 73 | 74 | typedef struct { 75 | int moduleApiVersion; 76 | RiruModuleInfo moduleInfo; 77 | } RiruVersionedModuleInfo; 78 | 79 | // --------------------------------------------------------- 80 | 81 | typedef struct { 82 | int riruApiVersion; 83 | void *unused; 84 | const char *magiskModulePath; 85 | int *allowUnload; 86 | } Riru; 87 | 88 | typedef RiruVersionedModuleInfo *(RiruInit_t)(Riru *); 89 | 90 | EXPORT void* init(Riru* arg); 91 | 92 | #ifdef __cplusplus 93 | } 94 | #endif 95 | 96 | #endif //DREAMLAND_RIRU_H 97 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/HiddenApis.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Build; 5 | import android.util.Log; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import java.lang.reflect.Method; 10 | 11 | /** 12 | * Created by canyie on 2019/11/24. 13 | */ 14 | public final class HiddenApis { 15 | private static final String TAG = "HiddenApis"; 16 | 17 | /** Result: Below Android P */ 18 | public static final int RESULT_IGNORED = 0; 19 | 20 | /** Result: Success! */ 21 | public static final int RESULT_SUCCESS = 1; 22 | 23 | /** Result: Failed... */ 24 | public static final int RESULT_FAILED = -1; 25 | 26 | private static Object sVMRuntime; 27 | private static Method setHiddenApiExemptions; 28 | 29 | private HiddenApis() {} 30 | 31 | static { 32 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 33 | try { 34 | Method getMethodMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class); 35 | getMethodMethod.setAccessible(true); 36 | Class clazz = Class.forName("dalvik.system.VMRuntime"); 37 | Method getRuntime = (Method) getMethodMethod.invoke(clazz, "getRuntime", null); 38 | getRuntime.setAccessible(true); 39 | sVMRuntime = getRuntime.invoke(null); 40 | setHiddenApiExemptions = (Method) getMethodMethod.invoke(clazz, "setHiddenApiExemptions", new Class[] {String[].class}); 41 | } catch(Exception e) { 42 | Log.e(TAG, "Error in getting setHiddenApiExemptions method", e); 43 | } 44 | } 45 | } 46 | 47 | /** 48 | * Exempt all hidden APIs access restrictions. 49 | * Note: Don't use dalvik bytecode to access the hidden APIs, 50 | * because it may be removed by the linker during verity. 51 | */ 52 | public static int exemptAll() { 53 | if(Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { 54 | Log.d(TAG, "Below Android P, ignoring"); 55 | return RESULT_IGNORED; 56 | } 57 | if(sVMRuntime == null || setHiddenApiExemptions == null) { 58 | Log.e(TAG, "sVMRuntime == null || setHiddenApiExemptions == null"); 59 | return RESULT_FAILED; 60 | } 61 | 62 | final String[] exemptions = {"L"}; 63 | // All java member's descriptors begin with a class's descriptor, like "Ljava/lang/Object;->getClass()V"; 64 | // and the prefix of all java class's descriptors is "L", like "Ljava/lang/Object;". 65 | // So we use "L" is equivalent to all java members. 66 | 67 | try { 68 | setHiddenApiExemptions.invoke(sVMRuntime, new Object[] { exemptions }); 69 | if (canAccessHiddenApis()) { 70 | return RESULT_SUCCESS; 71 | } 72 | Log.e(TAG, "Added 'L' to exemptions list but the hidden APIs is still inaccessible."); 73 | return RESULT_FAILED; 74 | } catch(Exception e) { 75 | Log.e(TAG,"Failed to call setHiddenApiExemptions method",e); 76 | } 77 | return RESULT_FAILED; 78 | } 79 | 80 | /** 81 | * Check the accessibility of hidden APIs. 82 | */ 83 | public static boolean canAccessHiddenApis() { 84 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { 85 | return true; 86 | } 87 | try { 88 | Class clazz = Class.forName("dalvik.system.VMRuntime"); 89 | Method method = clazz.getDeclaredMethod("setHiddenApiExemptions", String[].class); 90 | // VMRuntime.setHiddenApiExemptions in the hiddenapi-force-blacklist.txt 91 | method.setAccessible(true); 92 | return true; 93 | } catch (Exception ignored) { 94 | return false; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/utils/DLog.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.utils; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.util.Log; 5 | 6 | import top.canyie.pine.PineConfig; 7 | 8 | /** 9 | * @author canyie 10 | */ 11 | @SuppressLint("LogTagMismatch") 12 | @SuppressWarnings("WeakerAccess") 13 | public final class DLog { 14 | /** Whether to allow log output. Fatal levels are not limited by this. */ 15 | private static final boolean LOG_ENABLED = true; 16 | 17 | public static final int VERBOSE = Log.VERBOSE; 18 | public static final int DEBUG = Log.DEBUG; 19 | public static final int INFO = Log.INFO; 20 | public static final int WARN = Log.WARN; 21 | public static final int ERROR = Log.ERROR; 22 | public static final int FATAL = Log.ASSERT; 23 | 24 | public static void v(String tag, String msg, Object... format) { 25 | if (isLoggable(VERBOSE)) { 26 | Log.v(tag, String.valueOf(msg)); 27 | } 28 | } 29 | 30 | public static void d(String tag, String msg, Object... format) { 31 | if (isLoggable(DEBUG)) { 32 | Log.d(tag, String.format(msg, format)); 33 | } 34 | } 35 | 36 | public static void i(String tag, String msg, Object... format) { 37 | if (isLoggable(INFO)) { 38 | Log.i(tag, String.format(msg, format)); 39 | } 40 | } 41 | 42 | public static void w(String tag, String msg, Object... format) { 43 | if (isLoggable(WARN)) { 44 | Log.w(tag, String.format(msg, format)); 45 | } 46 | } 47 | 48 | public static void w(String tag, String msg, Throwable e) { 49 | if (isLoggable(WARN)) { 50 | Log.w(tag, msg, e); 51 | } 52 | } 53 | 54 | public static void w(String tag, Throwable e) { 55 | if (isLoggable(WARN)) { 56 | Log.w(tag, getStackTraceString(e)); 57 | } 58 | } 59 | 60 | public static void e(String tag, String msg, Object... format) { 61 | if (isLoggable(ERROR)) { 62 | Log.e(tag, String.format(msg, format)); 63 | } 64 | } 65 | 66 | public static void e(String tag, Throwable e) { 67 | if (isLoggable(ERROR)) { 68 | Log.e(tag, getStackTraceString(e)); 69 | } 70 | } 71 | 72 | public static void e(String tag, String msg, Throwable e) { 73 | if (isLoggable(ERROR)) { 74 | Log.e(tag, msg, e); 75 | } 76 | } 77 | 78 | /** 79 | * Print a fatal level log. It may cause the application process to terminate unexpectedly. 80 | */ 81 | public static void f(String tag, String msg, Object... format) { 82 | Log.wtf(tag, String.format(msg, format)); 83 | } 84 | 85 | /** 86 | * Print a fatal level log. It may cause the application process to terminate unexpectedly. 87 | */ 88 | public static void f(String tag, String msg, Throwable e) { 89 | Log.wtf(tag, msg, e); 90 | } 91 | 92 | /** 93 | * Print a fatal level log. It may cause the application process to terminate unexpectedly. 94 | */ 95 | public static void f(String tag, Throwable e) { 96 | Log.wtf(tag, e); 97 | } 98 | 99 | public static String getStackTraceString(Throwable e) { 100 | return Log.getStackTraceString(e); 101 | } 102 | 103 | public static void printStackTrace(String tag) { 104 | Log.e(tag, "here", new Throwable("here")); 105 | } 106 | 107 | public static boolean isLoggable(int level) { 108 | if (level == FATAL) { 109 | return true; 110 | } 111 | if (!LOG_ENABLED) { 112 | return false; 113 | } 114 | switch (level) { 115 | case VERBOSE: 116 | case DEBUG: 117 | return PineConfig.debug; 118 | } 119 | return true; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/services/DirectAccessService.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed.services; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | /** @hide */ 10 | public final class DirectAccessService extends BaseService { 11 | @Override 12 | public boolean hasDirectFileAccess() { 13 | return true; 14 | } 15 | 16 | @SuppressWarnings("RedundantIfStatement") 17 | @Override 18 | public boolean checkFileAccess(String filename, int mode) { 19 | File file = new File(filename); 20 | if (mode == F_OK && !file.exists()) return false; 21 | if ((mode & R_OK) != 0 && !file.canRead()) return false; 22 | if ((mode & W_OK) != 0 && !file.canWrite()) return false; 23 | if ((mode & X_OK) != 0 && !file.canExecute()) return false; 24 | return true; 25 | } 26 | 27 | @Override 28 | public boolean checkFileExists(String filename) { 29 | return new File(filename).exists(); 30 | } 31 | 32 | @Override 33 | public FileResult statFile(String filename) throws IOException { 34 | File file = new File(filename); 35 | return new FileResult(file.length(), file.lastModified()); 36 | } 37 | 38 | @Override 39 | public byte[] readFile(String filename) throws IOException { 40 | File file = new File(filename); 41 | byte[] content = new byte[(int)file.length()]; 42 | FileInputStream fis = new FileInputStream(file); 43 | fis.read(content); 44 | fis.close(); 45 | return content; 46 | } 47 | 48 | @Override 49 | public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException { 50 | File file = new File(filename); 51 | long size = file.length(); 52 | long time = file.lastModified(); 53 | if (previousSize == size && previousTime == time) 54 | return new FileResult(size, time); 55 | return new FileResult(readFile(filename), size, time); 56 | } 57 | 58 | @Override 59 | public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException { 60 | File file = new File(filename); 61 | long size = file.length(); 62 | long time = file.lastModified(); 63 | if (previousSize == size && previousTime == time) 64 | return new FileResult(size, time); 65 | 66 | // Shortcut for the simple case 67 | if (offset <= 0 && length <= 0) 68 | return new FileResult(readFile(filename), size, time); 69 | 70 | // Check range 71 | if (offset > 0 && offset >= size) { 72 | throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename); 73 | } else if (offset < 0) { 74 | offset = 0; 75 | } 76 | 77 | if (length > 0 && (offset + length) > size) { 78 | throw new IllegalArgumentException("Length " + length + " is out of range for " + filename); 79 | } else if (length <= 0) { 80 | length = (int) (size - offset); 81 | } 82 | 83 | byte[] content = new byte[length]; 84 | FileInputStream fis = new FileInputStream(file); 85 | fis.skip(offset); 86 | fis.read(content); 87 | fis.close(); 88 | return new FileResult(content, size, time); 89 | } 90 | 91 | /** 92 | * {@inheritDoc} 93 | *

This implementation returns a BufferedInputStream instead of loading the file into memory. 94 | */ 95 | @Override 96 | public InputStream getFileInputStream(String filename) throws IOException { 97 | return new BufferedInputStream(new FileInputStream(filename), 16*1024); 98 | } 99 | 100 | /** 101 | * {@inheritDoc} 102 | *

This implementation returns a BufferedInputStream instead of loading the file into memory. 103 | */ 104 | @Override 105 | public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException { 106 | File file = new File(filename); 107 | long size = file.length(); 108 | long time = file.lastModified(); 109 | if (previousSize == size && previousTime == time) 110 | return new FileResult(size, time); 111 | return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16*1024), size, time); 112 | } 113 | } -------------------------------------------------------------------------------- /template/languages.sh: -------------------------------------------------------------------------------- 1 | SIMPLIFIED_CHINESE=false 2 | if [ "$BOOTMODE" = "true" ]; then 3 | locale=$(getprop persist.sys.locale|awk -F "-" '{print $1"_"$NF}') 4 | [ "$locale" = "" ] && locale=$(settings get system system_locales|awk -F "," '{print $1}'|awk -F "-" '{print $1"_"$NF}') 5 | [ "$locale" = "zh_CN" ] && SIMPLIFIED_CHINESE=true 6 | fi 7 | 8 | if [ "$SIMPLIFIED_CHINESE" = "true" ]; then 9 | ALERT_ARCH="设备架构:" 10 | ALERT_ANDROID_API="Android API 级别:" 11 | ALERT_RIRU_API="Riru API 版本:" 12 | ALERT_BOOTMODE="设备已启动到 Android 系统" 13 | ALERT_DETECTING_FLAVOR="正在检测安装环境" 14 | ALERT_ZYGISK_FLAVOR="Zygisk 已启用,将安装为 Zygisk 版本" 15 | ALERT_RIRU_FLAVOR="Zygisk 未启用,将安装为 Riru 版本" 16 | ALERT_EXTRACT_MODULE_FILES="正在解压模块文件" 17 | ALERT_REMOVE_LIB64="正在移除 64 位支持库" 18 | ALERT_FLAVOR_SPECIFC="开始 flavor 特定安装" 19 | ALERT_PREPARE_LOCAL_DIR="正在准备配置目录" 20 | ALRET_REMOVE_SEPOLICY_1="Magisk 版本低于 20.2,正在移除 sepolicy.rule" 21 | ALRET_REMOVE_SEPOLICY_2="我们建议您升级 Magisk" 22 | ALERT_REBOOT_TWICE_1="Magisk 版本低于 21006" 23 | ALERT_REBOOT_TWICE_2="您可能需要重启设备两次才能工作" 24 | ALERT_SETTING_PERMISSIONS="正在设置文件权限" 25 | ALERT_OLD_RIRU="较旧的 Riru API 版本:" 26 | ALERT_REMOVE_OLD_FOR_NEW_RIRU="Riru v25+,正在移除旧的模块路径" 27 | 28 | ERR_UNSUPPORTED_ARCH="不支持的设备架构:" 29 | ERR_UNSUPPORTED_ANDROID_API="不支持的Android API级别:" 30 | ERR_FLASH_FROM_RECOVERY_1="从 Recovery 刷入不再是受支持的安装方法" 31 | ERR_FLASH_FROM_RECOVERY_2="由于 Recovery 本身限制,它可能会引发一些奇怪问题" 32 | ERR_FLASH_FROM_RECOVERY_3="请从 Magisk app 安装模块而非 Recovery" 33 | ERR_ZYGISK_REQUIRES_24="Zygisk 版本只支持 Magisk v24+" 34 | ERR_NO_FLAVOR="请在 Magisk app 中打开 Zygisk 并重启或安装模块 'Riru'" 35 | ERR_UNSUPPORTED_RIRU_API="不支持的 Riru API版本:" 36 | ERR_EXTRACT_MODULE_FILES="解压模块文件失败:" 37 | ERR_EXTRACT_SYSTEM_FOLDER="解压system目录失败:" 38 | ERR_FLAVOR_SPECIFC="无法进行 flavor 特定安装:" 39 | ERR_COPY_PROP_TO_RIRU_MODULE_PATH="复制module.prop失败:" 40 | ERR_PREPARE_LOCAL_DIR="无法创建配置目录:" 41 | 42 | WARN_OLD_ANDROID_API="未测试,可能不支持的 Android API " 43 | WARN_OLD_MANAGER_1="您安装了已弃用的旧版管理器" 44 | WARN_OLD_MANAGER_2="它与您正在安装的梦境框架不兼容" 45 | WARN_MANAGER_NOT_INSTALLED_1="梦境管理器未安装" 46 | WARN_MANAGER_NOT_INSTALLED_2="您将无法管理框架设置" 47 | WARN_PLEASE_INSTALL_NEW_MANAGER="请安装新版管理器!" 48 | else 49 | ALERT_ARCH="Device architecture:" 50 | ALERT_ANDROID_API="Android API level:" 51 | ALERT_RIRU_API="Riru API version:" 52 | ALERT_BOOTMODE="Device is booted to Android system" 53 | ALERT_DETECTING_FLAVOR="Detecting installation flavor" 54 | ALERT_ZYGISK_FLAVOR="Zygisk is enabled, installing as Zygisk flavor" 55 | ALERT_RIRU_FLAVOR="Zygisk not enabled, installing as Riru flavor" 56 | ALERT_EXTRACT_MODULE_FILES="Extracting module files" 57 | ALERT_REMOVE_LIB64="Removing 64-bit libraries" 58 | ALERT_FLAVOR_SPECIFC="Starting flavor specific installations" 59 | ALERT_PREPARE_LOCAL_DIR="Preparing local directory" 60 | ALRET_REMOVE_SEPOLICY_1="Removing sepolicy because of your Magisk version is lower than 20.2" 61 | ALRET_REMOVE_SEPOLICY_2="We recommend that you upgrade Magisk to the latest version" 62 | ALERT_REBOOT_TWICE_1="Magisk version below 21006" 63 | ALERT_REBOOT_TWICE_2="You may need to manually reboot twice" 64 | ALERT_SETTING_PERMISSIONS="Setting permissions" 65 | ALERT_OLD_RIRU="Old Riru API version: " 66 | ALERT_REMOVE_OLD_FOR_NEW_RIRU="Removing old module path for Riru v25+" 67 | 68 | ERR_UNSUPPORTED_ARCH="Unsupported architecture:" 69 | ERR_UNSUPPORTED_ANDROID_API="Unsupported Android API level" 70 | ERR_FLASH_FROM_RECOVERY_1="NO longer support installing from Recovery" 71 | ERR_FLASH_FROM_RECOVERY_2="Recovery sucks, and installing from it may cause weird problems" 72 | ERR_FLASH_FROM_RECOVERY_3="Please install module from Magisk app instead of Recovery" 73 | ERR_ZYGISK_REQUIRES_24="Zygisk flavor requires Magisk v24+" 74 | ERR_NO_FLAVOR="Please turn on Zygisk from Magisk app and reboot, or install module 'Riru'" 75 | ERR_UNSUPPORTED_RIRU_API="Unsupported Riru API version" 76 | ERR_EXTRACT_MODULE_FILES="Can't extract module files:" 77 | ERR_EXTRACT_SYSTEM_FOLDER="Can't extract the system folder:" 78 | ERR_FLAVOR_SPECIFC="Unable to do flavor specific installation" 79 | ERR_COPY_PROP_TO_RIRU_MODULE_PATH="Can't copy module.prop to the riru module path:" 80 | ERR_PREPARE_LOCAL_DIR="Can't create the local directory:" 81 | 82 | WARN_OLD_ANDROID_API="Dreamland framework is not completely tested with your Android API " 83 | WARN_OLD_MANAGER_1="Detected deprecated dreamland manager" 84 | WARN_OLD_MANAGER_2="It is not compatible with current framework version" 85 | WARN_MANAGER_NOT_INSTALLED_1="Dreamland Manager not found" 86 | WARN_MANAGER_NOT_INSTALLED_2="You cannot manage Dreamland configurations" 87 | WARN_PLEASE_INSTALL_NEW_MANAGER="Please install new Dreamland Manager at https://github.com/canyie/DreamlandManager/releases" 88 | fi 89 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/binder.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2021/1/28. 3 | // 4 | 5 | #include "binder.h" 6 | #include "../utils/scoped_local_ref.h" 7 | #include "../utils/log.h" 8 | 9 | using namespace dreamland; 10 | 11 | jclass Binder::ServiceManager = nullptr; 12 | jclass Binder::IBinder = nullptr; 13 | jclass Binder::Parcel = nullptr; 14 | jmethodID Binder::getService = nullptr; 15 | jmethodID Binder::transact = nullptr; 16 | jmethodID Binder::obtainParcel = nullptr; 17 | jmethodID Binder::writeInterfaceToken = nullptr; 18 | jmethodID Binder::readException = nullptr; 19 | jmethodID Binder::readStrongBinder = nullptr; 20 | jmethodID Binder::recycleParcel = nullptr; 21 | jstring Binder::serviceName = nullptr; 22 | jstring Binder::interfaceToken = nullptr; 23 | 24 | template 25 | static T MakeGlobalRef(JNIEnv* env, T local) { 26 | T global = (T) env->NewGlobalRef(local); 27 | env->DeleteLocalRef(local); 28 | return global; 29 | } 30 | 31 | template 32 | static void FreeGlobalRef(JNIEnv* env, T& ref) { 33 | env->DeleteGlobalRef(ref); 34 | ref = nullptr; 35 | } 36 | 37 | bool Binder::Prepare(JNIEnv* env) { 38 | ServiceManager = MakeGlobalRef(env, env->FindClass("android/os/ServiceManager")); 39 | IBinder = MakeGlobalRef(env, env->FindClass("android/os/IBinder")); 40 | Parcel = MakeGlobalRef(env, env->FindClass("android/os/Parcel")); 41 | 42 | getService = env->GetStaticMethodID(ServiceManager, "getService", "(Ljava/lang/String;)Landroid/os/IBinder;"); 43 | transact = env->GetMethodID(IBinder, "transact", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z"); 44 | obtainParcel = env->GetStaticMethodID(Parcel, "obtain", "()Landroid/os/Parcel;"); 45 | writeInterfaceToken = env->GetMethodID(Parcel, "writeInterfaceToken", "(Ljava/lang/String;)V"); 46 | readException = env->GetMethodID(Parcel, "readException", "()V"); 47 | readStrongBinder = env->GetMethodID(Parcel, "readStrongBinder", "()Landroid/os/IBinder;"); 48 | recycleParcel = env->GetMethodID(Parcel, "recycle", "()V"); 49 | 50 | serviceName = MakeGlobalRef(env, env->NewStringUTF(kServiceName)); 51 | interfaceToken = MakeGlobalRef(env, env->NewStringUTF(kInterfaceToken)); 52 | return true; 53 | } 54 | 55 | jobject Binder::GetBinder(JNIEnv* env) { 56 | // TODO: JNI is very slow, use pure-native code instead if we can. 57 | #define FAIL_IF(cond, error_msg) do {\ 58 | if (UNLIKELY(cond)) {\ 59 | LOGE(error_msg);\ 60 | goto fail;\ 61 | }\ 62 | \ 63 | } while (0) 64 | 65 | #define FAIL_IF_EXCEPTION(error_msg) FAIL_IF(env->ExceptionCheck(), error_msg) 66 | 67 | ScopedLocalRef clipboard(env); 68 | ScopedLocalRef data(env); 69 | ScopedLocalRef reply(env); 70 | jboolean success; 71 | jobject service = nullptr; 72 | 73 | clipboard.Reset(env->CallStaticObjectMethod(ServiceManager, getService, serviceName)); 74 | FAIL_IF_EXCEPTION("ServiceManager.getService threw exception"); 75 | 76 | if (UNLIKELY(clipboard.IsNull())) { 77 | // Isolated process or google gril service process is not allowed to access clipboard service 78 | LOGW("Clipboard service is unavailable in current process, skipping"); 79 | return nullptr; 80 | } 81 | 82 | data.Reset(env->CallStaticObjectMethod(Parcel, obtainParcel)); 83 | FAIL_IF(data.IsNull(), "Failed to obtain data parcel"); 84 | reply.Reset(env->CallStaticObjectMethod(Parcel, obtainParcel)); 85 | FAIL_IF(reply.IsNull(), "Failed to obtain reply parcel"); 86 | 87 | env->CallVoidMethod(data.Get(), writeInterfaceToken, interfaceToken); 88 | FAIL_IF_EXCEPTION("Parcel.writeInterfaceToken threw exception"); 89 | 90 | success = env->CallBooleanMethod(clipboard.Get(), transact, kTransactionCode, data.Get(), reply.Get(), 0); 91 | FAIL_IF_EXCEPTION("Binder.transact threw exception"); 92 | 93 | env->CallVoidMethod(reply.Get(), readException); 94 | FAIL_IF_EXCEPTION("Clipboard service threw exception"); 95 | 96 | if (UNLIKELY(success)) { 97 | service = env->CallObjectMethod(reply.Get(), readStrongBinder); 98 | FAIL_IF_EXCEPTION("readStrongBinder threw exception"); 99 | } 100 | 101 | RecycleParcel(env, data.Get()); 102 | RecycleParcel(env, reply.Get()); 103 | 104 | return service; 105 | 106 | fail: 107 | env->ExceptionDescribe(); 108 | env->ExceptionClear(); 109 | RecycleParcel(env, data.Get()); 110 | RecycleParcel(env, reply.Get()); 111 | return nullptr; 112 | } 113 | 114 | void Binder::Cleanup(JNIEnv* env) { 115 | FreeGlobalRef(env, ServiceManager); 116 | FreeGlobalRef(env, IBinder); 117 | FreeGlobalRef(env, Parcel); 118 | FreeGlobalRef(env, serviceName); 119 | FreeGlobalRef(env, interfaceToken); 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/de/robv/android/xposed/SELinuxHelper.java: -------------------------------------------------------------------------------- 1 | package de.robv.android.xposed; 2 | 3 | import android.os.SELinux; 4 | import android.system.OsConstants; 5 | import android.util.Log; 6 | 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | 10 | import de.robv.android.xposed.services.BaseService; 11 | import de.robv.android.xposed.services.DirectAccessService; 12 | import top.canyie.dreamland.utils.IOUtils; 13 | 14 | import static top.canyie.dreamland.core.Dreamland.TAG; 15 | 16 | /** 17 | * A helper to work with (or without) SELinux, abstracting much of its big complexity. 18 | */ 19 | public final class SELinuxHelper { 20 | private static boolean sIsSELinuxEnabled = false; 21 | // Dreamland changed: Only supports DirectAccessService 22 | private static BaseService sServiceAppDataFile = new DirectAccessService(); 23 | 24 | // Dreamland changed: sIsSELinuxEnabled will be initialized in static block 25 | static { 26 | try { 27 | sIsSELinuxEnabled = SELinux.isSELinuxEnabled(); 28 | } catch (NoClassDefFoundError ignored) { 29 | } 30 | } 31 | 32 | private SELinuxHelper() {} 33 | 34 | // Dreamland changed: Don't use SELinuxHelper.initOnce(), sIsSELinuxEnabled will be initialized in static block 35 | // public static void initOnce() { 36 | // try { 37 | // sIsSELinuxEnabled = SELinux.isSELinuxEnabled(); 38 | // } catch (NoClassDefFoundError ignored) {} 39 | // } 40 | 41 | 42 | /** 43 | * Determines whether SELinux is permissive or enforcing. 44 | * 45 | * @return A boolean indicating whether SELinux is enforcing. 46 | */ 47 | public static boolean isSELinuxEnforced() { 48 | // Dreamland changed because SELinux.isSELinuxEnforced() may return incorrect value 49 | // return sIsSELinuxEnabled && SELinux.isSELinuxEnforced(); 50 | 51 | if (!sIsSELinuxEnabled) return false; 52 | FileInputStream fis = null; 53 | try { 54 | fis = new FileInputStream("/sys/fs/selinux/enforce"); 55 | int status = fis.read(); 56 | switch (status) { 57 | case '1': 58 | return true; 59 | case '0': 60 | return false; 61 | default: 62 | Log.e(TAG, "Unexpected byte " + status + " in /sys/fs/selinux/enforce"); 63 | break; 64 | } 65 | } catch (IOException e) { 66 | int errno = IOUtils.getErrno(e); 67 | if (errno == OsConstants.EACCES || errno == OsConstants.EPERM) { 68 | // Status file is existing but cannot read, blocked by SELinux? 69 | Log.w(TAG, "Read /sys/fs/selinux/enforce failed: permission denied."); 70 | return true; 71 | } 72 | Log.e(TAG, "Read SELinux status file failed", e); 73 | } finally { 74 | IOUtils.closeQuietly(fis); 75 | } 76 | 77 | return SELinux.isSELinuxEnforced(); 78 | } 79 | 80 | /** 81 | * Gets the security context of the current process. 82 | * 83 | * @return A String representing the security context of the current process. 84 | */ 85 | public static String getContext() { 86 | return sIsSELinuxEnabled ? SELinux.getContext() : null; 87 | } 88 | 89 | /** 90 | * Retrieve the service to be used when accessing files in {@code /data/data/*}. 91 | * 92 | *

IMPORTANT: If you call this from the Zygote process, 93 | * don't re-use the result in different process! 94 | * 95 | * @return An instance of the service. 96 | */ 97 | public static BaseService getAppDataFileService() { 98 | // Dreamland changed: Only supports DirectAccessService 99 | // if (sServiceAppDataFile != null) 100 | // return sServiceAppDataFile; 101 | // throw new UnsupportedOperationException(); 102 | return sServiceAppDataFile; 103 | } 104 | 105 | 106 | // Dreamland changed: Only supports DirectAccessService 107 | // /*package*/ static void initForProcess(String packageName) { 108 | // if (sIsSELinuxEnabled) { 109 | // if (packageName == null) { // Zygote 110 | // sServiceAppDataFile = new ZygoteService(); 111 | // } else if (packageName.equals("android")) { //system_server 112 | // sServiceAppDataFile = BinderService.getService(BinderService.TARGET_APP); 113 | // } else { // app 114 | // sServiceAppDataFile = new DirectAccessService(); 115 | // } 116 | // } else { 117 | // sServiceAppDataFile = new DirectAccessService(); 118 | // } 119 | // } 120 | } 121 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/core/BaseManager.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.core; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import top.canyie.dreamland.utils.AppGlobals; 7 | import top.canyie.dreamland.utils.DLog; 8 | import top.canyie.dreamland.utils.IOUtils; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author canyie 15 | */ 16 | @SuppressWarnings("WeakerAccess") 17 | public abstract class BaseManager { 18 | private static final String TAG = "BaseManager"; 19 | private final File mFile; 20 | private final File mBackupFile; 21 | private final Object mLock = new Object(); 22 | private final Object mWritingToDiskLock = new Object(); 23 | private T mObject; 24 | private volatile boolean mLoaded; 25 | 26 | protected BaseManager(String filename) { 27 | mFile = new File(Dreamland.BASE_DIR, filename); 28 | mBackupFile = new File(Dreamland.BASE_DIR,filename + ".bak"); 29 | } 30 | 31 | public File getFile() { 32 | return mFile; 33 | } 34 | 35 | @NonNull protected T getRealObject() { 36 | ensureDataLoaded(); 37 | return mObject; 38 | } 39 | 40 | public void notifyDataChanged() { 41 | T obj = this.mObject; 42 | Runnable action = () -> { 43 | synchronized (mWritingToDiskLock) { 44 | try { 45 | writeToFile(obj); 46 | } catch (IOException e) { 47 | DLog.e(TAG, "!!! Failed to write " + mFile.getAbsolutePath() + " !!!", e); 48 | } 49 | } 50 | }; 51 | AppGlobals.getDefaultExecutor().execute(action); 52 | } 53 | 54 | @NonNull protected abstract String serialize(T obj); 55 | @Nullable protected abstract T deserialize(String str); 56 | @NonNull protected abstract T createEmpty(); 57 | 58 | public void loadFromDisk() { 59 | this.mLoaded = false; 60 | T obj = readObjectFromDisk(); 61 | synchronized (mLock) { 62 | this.mObject = obj; 63 | mLoaded = true; 64 | mLock.notifyAll(); 65 | } 66 | } 67 | 68 | @NonNull protected T readObjectFromDisk() { 69 | T obj = null; 70 | try { 71 | obj = readFromDisk(); 72 | } catch (IOException e) { 73 | DLog.e(TAG, "!!! Failed to read " + mFile.getAbsolutePath() + " !!!", e); 74 | } 75 | if (obj == null) { 76 | obj = createEmpty(); 77 | } 78 | return obj; 79 | } 80 | 81 | @Nullable private T readFromDisk() throws IOException { 82 | restoreFile(); 83 | if (!mFile.exists()) { 84 | return null; 85 | } 86 | String content = IOUtils.readAllString(mFile); 87 | return deserialize(content); 88 | } 89 | 90 | public void ensureDataLoaded() { 91 | if (mLoaded) return; 92 | synchronized (mLock) { 93 | if (mLoaded) return; 94 | boolean interrupted = false; 95 | try { 96 | while (!mLoaded) { 97 | try { 98 | mLock.wait(); 99 | } catch (InterruptedException e) { 100 | interrupted = true; 101 | } 102 | } 103 | } finally { 104 | if (interrupted) 105 | Thread.currentThread().interrupt(); 106 | } 107 | } 108 | } 109 | 110 | private void writeToFile(T obj) throws IOException { 111 | backup(); 112 | String content = serialize(obj); 113 | IOUtils.writeToFile(mFile, content); 114 | deleteBackupFile(); 115 | } 116 | 117 | private void backup() { 118 | if (mBackupFile.exists()) { 119 | DLog.e(TAG, "Don't backup %s: the backup file is already exists. Did the last write fail?", mFile.getAbsolutePath()); 120 | // noinspection ResultOfMethodCallIgnored 121 | mFile.delete(); 122 | return; 123 | } 124 | if (!mFile.exists()) { 125 | return; 126 | } 127 | if (!mFile.renameTo(mBackupFile)) { 128 | DLog.e(TAG, "Failed to backup %s: rename file failed.", mFile.getAbsolutePath()); 129 | } 130 | } 131 | 132 | private void deleteBackupFile() { 133 | if (!mBackupFile.delete() && mBackupFile.exists()) 134 | DLog.e(TAG, "Failed to delete backup file %s.", mBackupFile.getAbsolutePath()); 135 | } 136 | 137 | private void restoreFile() { 138 | if (mBackupFile.exists()) { 139 | DLog.e(TAG, "backup file %s is exists, did the last write fail?", mBackupFile.getAbsolutePath()); 140 | if (!mFile.delete() && mFile.exists()) { 141 | DLog.e(TAG, "Delete file failed... "); 142 | } 143 | if (!mBackupFile.renameTo(mFile)) { 144 | DLog.e(TAG, "Restore file failed: rename file failed."); 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/core/ModuleManager.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.core; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import java.io.IOException; 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | import java.util.zip.ZipFile; 13 | 14 | import top.canyie.dreamland.ipc.ModuleInfo; 15 | 16 | /** 17 | * @author canyie 18 | */ 19 | public final class ModuleManager extends GsonBasedManager> { 20 | public ModuleManager() { 21 | super("modules.json"); 22 | } 23 | 24 | public Map getModules() { 25 | // Note: From the principle of encapsulation, the real object should not be returned directly, 26 | // we should return a unmodifiable object, but this is not a public API 27 | // and we know that no write operation will be performed on the return value. 28 | // return new HashMap<>(getRealObject()); 29 | return getRealObject(); 30 | } 31 | 32 | public String[] getScopeFor(String packageName) { 33 | ModuleInfo module = getRealObject().get(packageName); 34 | if (module == null) return null; 35 | return module.getScope(); 36 | } 37 | 38 | public void setScopeFor(String packageName, String[] apps) { 39 | Map map = getRealObject(); 40 | ModuleInfo moduleInfo = map.get(packageName); 41 | if (moduleInfo == null) { 42 | moduleInfo = new ModuleInfo(); 43 | moduleInfo.enabled = false; 44 | // Not found in map, this module must not be enabled. 45 | // Module path will set in enable(). 46 | map.put(packageName, moduleInfo); 47 | } 48 | moduleInfo.setScope(apps); 49 | notifyDataChanged(); 50 | } 51 | 52 | public Set getAllEnabled() { 53 | // Note: From the principle of encapsulation, the real object should not be returned directly, 54 | // we should return a unmodifiable object, but this is not a public API 55 | // and we know that no write operation will be performed on the return value. 56 | Map map = getRealObject(); 57 | if (map.isEmpty()) return Collections.emptySet(); 58 | Set result = new HashSet<>(Math.min(4, map.size() / 2)); 59 | for (Map.Entry entry : map.entrySet()) { 60 | if (entry.getValue().enabled) 61 | result.add(entry.getKey()); 62 | } 63 | return result; 64 | } 65 | 66 | public void getScopeFor(String packageName, Set out) { 67 | Map map = getRealObject(); 68 | if (map.isEmpty()) return; 69 | Collection modules = map.values(); 70 | for (ModuleInfo module : modules) { 71 | if (module.enabled && module.isEnabledFor(packageName)) 72 | out.add(module); 73 | } 74 | } 75 | 76 | public boolean isModuleEnabled(String packageName) { 77 | ModuleInfo moduleInfo = getRealObject().get(packageName); 78 | return moduleInfo != null && moduleInfo.enabled; 79 | } 80 | 81 | public void enable(String module, String path, String nativeDir) { 82 | Map map = getRealObject(); 83 | ModuleInfo moduleInfo = map.get(module); 84 | if (moduleInfo == null) { 85 | map.put(module, new ModuleInfo(path, nativeDir)); 86 | } else { 87 | moduleInfo.path = path; // Module path maybe changed 88 | moduleInfo.nativePath = nativeDir; 89 | moduleInfo.enabled = true; 90 | } 91 | notifyDataChanged(); 92 | } 93 | 94 | public void disable(String packageName) { 95 | ModuleInfo moduleInfo = getRealObject().get(packageName); 96 | if (moduleInfo != null) { 97 | moduleInfo.enabled = false; 98 | notifyDataChanged(); 99 | } 100 | } 101 | 102 | public void updateModulePath(String packageName, String path, String nativeDir) { 103 | ModuleInfo moduleInfo = getRealObject().get(packageName); 104 | if (moduleInfo != null) { 105 | moduleInfo.path = path; 106 | moduleInfo.nativePath = nativeDir; 107 | notifyDataChanged(); 108 | } 109 | } 110 | 111 | public boolean remove(String packageName) { 112 | ModuleInfo moduleInfo = getRealObject().remove(packageName); 113 | if (moduleInfo != null) { 114 | notifyDataChanged(); 115 | return true; 116 | } 117 | return false; 118 | } 119 | 120 | @NonNull @Override protected ConcurrentHashMap createEmpty() { 121 | return new ConcurrentHashMap<>(); 122 | } 123 | 124 | public static boolean isModuleValid(String apk) { 125 | try (ZipFile zip = new ZipFile(apk)) { 126 | return zip.getEntry("assets/xposed_init") != null; 127 | } catch (IOException e) { 128 | return false; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /template/META-INF/com/google/android/update-binary: -------------------------------------------------------------------------------- 1 | #!/sbin/sh 2 | 3 | ################# 4 | # Initialization 5 | ################# 6 | 7 | umask 022 8 | 9 | # echo before loading util_functions 10 | ui_print() { echo "$1"; } 11 | 12 | require_new_magisk() { 13 | ui_print "*******************************" 14 | ui_print " Please install Magisk v20.0+! " 15 | ui_print "*******************************" 16 | exit 1 17 | } 18 | 19 | ######################### 20 | # Load util_functions.sh 21 | ######################### 22 | 23 | OUTFD=$2 24 | ZIPFILE=$3 25 | 26 | mount /data 2>/dev/null 27 | 28 | [ -f /data/adb/magisk/util_functions.sh ] || require_new_magisk 29 | . /data/adb/magisk/util_functions.sh 30 | [ $MAGISK_VER_CODE -lt 20000 ] && require_new_magisk 31 | 32 | if [ $MAGISK_VER_CODE -ge 20400 ]; then 33 | # New Magisk have complete installation logic within util_functions.sh 34 | install_module 35 | exit 0 36 | fi 37 | 38 | ################# 39 | # Legacy Support 40 | ################# 41 | 42 | TMPDIR=/dev/tmp 43 | PERSISTDIR=/sbin/.magisk/mirror/persist 44 | 45 | is_legacy_script() { 46 | unzip -l "$ZIPFILE" install.sh | grep -q install.sh 47 | return $? 48 | } 49 | 50 | print_modname() { 51 | local authlen len namelen pounds 52 | namelen=`echo -n $MODNAME | wc -c` 53 | authlen=$((`echo -n $MODAUTH | wc -c` + 3)) 54 | [ $namelen -gt $authlen ] && len=$namelen || len=$authlen 55 | len=$((len + 2)) 56 | pounds=$(printf "%${len}s" | tr ' ' '*') 57 | ui_print "$pounds" 58 | ui_print " $MODNAME " 59 | ui_print " by $MODAUTH " 60 | ui_print "$pounds" 61 | ui_print "*******************" 62 | ui_print " Powered by Magisk " 63 | ui_print "*******************" 64 | } 65 | 66 | # Override abort as old scripts have some issues 67 | abort() { 68 | ui_print "$1" 69 | $BOOTMODE || recovery_cleanup 70 | [ -n $MODPATH ] && rm -rf $MODPATH 71 | rm -rf $TMPDIR 72 | exit 1 73 | } 74 | 75 | rm -rf $TMPDIR 2>/dev/null 76 | mkdir -p $TMPDIR 77 | 78 | # Preperation for flashable zips 79 | setup_flashable 80 | 81 | # Mount partitions 82 | mount_partitions 83 | 84 | # Detect version and architecture 85 | api_level_arch_detect 86 | 87 | # Setup busybox and binaries 88 | $BOOTMODE && boot_actions || recovery_actions 89 | 90 | ############## 91 | # Preparation 92 | ############## 93 | 94 | # Extract prop file 95 | unzip -o "$ZIPFILE" module.prop -d $TMPDIR >&2 96 | [ ! -f $TMPDIR/module.prop ] && abort "! Unable to extract zip file!" 97 | 98 | $BOOTMODE && MODDIRNAME=modules_update || MODDIRNAME=modules 99 | MODULEROOT=$NVBASE/$MODDIRNAME 100 | MODID=`grep_prop id $TMPDIR/module.prop` 101 | MODNAME=`grep_prop name $TMPDIR/module.prop` 102 | MODAUTH=`grep_prop author $TMPDIR/module.prop` 103 | MODPATH=$MODULEROOT/$MODID 104 | 105 | # Create mod paths 106 | rm -rf $MODPATH 2>/dev/null 107 | mkdir -p $MODPATH 108 | 109 | ########## 110 | # Install 111 | ########## 112 | 113 | if is_legacy_script; then 114 | unzip -oj "$ZIPFILE" module.prop install.sh uninstall.sh 'common/*' -d $TMPDIR >&2 115 | 116 | # Load install script 117 | . $TMPDIR/install.sh 118 | 119 | # Callbacks 120 | print_modname 121 | on_install 122 | 123 | # Custom uninstaller 124 | [ -f $TMPDIR/uninstall.sh ] && cp -af $TMPDIR/uninstall.sh $MODPATH/uninstall.sh 125 | 126 | # Skip mount 127 | $SKIPMOUNT && touch $MODPATH/skip_mount 128 | 129 | # prop file 130 | $PROPFILE && cp -af $TMPDIR/system.prop $MODPATH/system.prop 131 | 132 | # Module info 133 | cp -af $TMPDIR/module.prop $MODPATH/module.prop 134 | 135 | # post-fs-data scripts 136 | $POSTFSDATA && cp -af $TMPDIR/post-fs-data.sh $MODPATH/post-fs-data.sh 137 | 138 | # service scripts 139 | $LATESTARTSERVICE && cp -af $TMPDIR/service.sh $MODPATH/service.sh 140 | 141 | ui_print "- Setting permissions" 142 | set_permissions 143 | else 144 | print_modname 145 | 146 | unzip -o "$ZIPFILE" customize.sh -d $MODPATH >&2 147 | 148 | if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then 149 | ui_print "- Extracting module files" 150 | unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2 151 | 152 | # Default permissions 153 | set_perm_recursive $MODPATH 0 0 0755 0644 154 | fi 155 | 156 | # Load customization script 157 | [ -f $MODPATH/customize.sh ] && . $MODPATH/customize.sh 158 | fi 159 | 160 | # Handle replace folders 161 | for TARGET in $REPLACE; do 162 | ui_print "- Replace target: $TARGET" 163 | mktouch $MODPATH$TARGET/.replace 164 | done 165 | 166 | if $BOOTMODE; then 167 | # Update info for Magisk Manager 168 | mktouch $NVBASE/modules/$MODID/update 169 | cp -af $MODPATH/module.prop $NVBASE/modules/$MODID/module.prop 170 | fi 171 | 172 | # Copy over custom sepolicy rules 173 | if [ -f $MODPATH/sepolicy.rule -a -e $PERSISTDIR ]; then 174 | ui_print "- Installing custom sepolicy patch" 175 | # Remove old recovery logs (which may be filling partition) to make room 176 | rm -f $PERSISTDIR/cache/recovery/* 177 | PERSISTMOD=$PERSISTDIR/magisk/$MODID 178 | mkdir -p $PERSISTMOD 179 | cp -af $MODPATH/sepolicy.rule $PERSISTMOD/sepolicy.rule || abort "! Insufficient partition size" 180 | fi 181 | 182 | # Remove stuffs that don't belong to modules 183 | rm -rf \ 184 | $MODPATH/system/placeholder $MODPATH/customize.sh \ 185 | $MODPATH/README.md $MODPATH/.git* 2>/dev/null 186 | 187 | ############# 188 | # Finalizing 189 | ############# 190 | 191 | cd / 192 | $BOOTMODE || recovery_cleanup 193 | rm -rf $TMPDIR 194 | 195 | ui_print "- Done" 196 | exit 0 197 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/native_hook.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2024/2/5. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "native_hook.h" 12 | #include "../utils/log.h" 13 | #include "../utils/macros.h" 14 | #include "../utils/scoped_elf.h" 15 | 16 | // --- Native Hook API definitions --- 17 | 18 | typedef int (*HookFunType)(void* func, void* replace, void** backup); 19 | 20 | typedef int (*UnhookFunType)(void* func); 21 | 22 | typedef void (*NativeOnModuleLoaded)(const char* name, void* handle); 23 | 24 | typedef struct { 25 | uint32_t version; 26 | HookFunType hook_func; 27 | UnhookFunType unhook_func; 28 | } NativeAPIEntries; 29 | 30 | typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries* entries); 31 | 32 | // ----------------------------------- 33 | 34 | static const size_t page_size = static_cast(sysconf(_SC_PAGESIZE)); 35 | static std::list entrypoints; 36 | static std::list module_loaded_callbacks; 37 | static void* dlopen_backup; 38 | 39 | namespace dreamland { 40 | static int HookFunction(void* func, void* replace, void** backup) { 41 | LOGD("Module hooking %p with %p, backup to %p", func, replace, backup); 42 | // Always re-protect the target page as rwx to bypass a dobby's bug 43 | size_t alignment = (uintptr_t) func % page_size; 44 | void* aligned_ptr = (void*) ((uintptr_t) func - alignment); 45 | mprotect(aligned_ptr, page_size, PROT_READ | PROT_WRITE | PROT_EXEC); 46 | return DobbyHook(func, replace, backup); 47 | } 48 | 49 | static int UnhookFunction(void* func) { 50 | LOGD("Module unhooking %p", func); 51 | return DobbyDestroy(func); 52 | } 53 | 54 | static const NativeAPIEntries api_entries { 55 | .version = 2, 56 | .hook_func = HookFunction, 57 | .unhook_func = UnhookFunction, 58 | }; 59 | 60 | void SoLoaded(const char* name, void* handle) { 61 | if (!handle) [[unlikely]] return; 62 | std::string_view path(name ? name : "NULL"); 63 | for (std::string_view module : entrypoints) { 64 | auto l = path.length(); 65 | auto r = module.length(); 66 | if (l >= r && path.compare(l - r, r, module) == 0) { 67 | // path ends with module, so this is a module library 68 | LOGD("Loading module library %s: %p", module.data(), handle); 69 | NativeInit native_init = reinterpret_cast(dlsym(handle, "native_init")); 70 | NativeOnModuleLoaded callback; 71 | if (native_init && (callback = native_init(&api_entries))) { 72 | module_loaded_callbacks.emplace_back(callback); 73 | return; // return directly to avoid module interaction 74 | } 75 | } 76 | } 77 | 78 | for (auto& callback : module_loaded_callbacks) 79 | callback(name, handle); 80 | } 81 | 82 | void* DoDlopenHook(const char* name, int flags, const void* extinfo, const void* caller) { 83 | void* handle = reinterpret_cast(dlopen_backup) 84 | (name, flags, extinfo, caller); 85 | SoLoaded(name, handle); 86 | return handle; 87 | } 88 | 89 | void* DlopenHook(const char* name, int flags) { 90 | void* handle = reinterpret_cast(dlopen_backup)(name, flags); 91 | SoLoaded(name, handle); 92 | return handle; 93 | } 94 | 95 | static void Main_recordNativeEntrypoint(JNIEnv* env, jclass, jstring lib) { 96 | static bool initialized = []() { 97 | // Do not directly hook dlopen, changing its caller will change its linker namespace 98 | // and cause some system libraries fail to load. 99 | const char* linker_path = LP_SELECT("/apex/com.android.runtime/bin/linker64", 100 | "/apex/com.android.runtime/bin/linker"); 101 | if (access(linker_path, F_OK)) { 102 | linker_path = LP_SELECT("/system/bin/linker64", "/system/bin/linker"); 103 | } 104 | ScopedElf linker(linker_path); 105 | void* target; 106 | void* hook; 107 | 108 | // do_dlopen on Android 8.0+ 109 | if (linker.GetSymbolAddress("__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv", &target) 110 | // do_dlopen on Android 7.x 111 | || linker.GetSymbolAddress("__dl__Z9do_dlopenPKciPK17android_dlextinfoPv", &target)) { 112 | hook = reinterpret_cast(DoDlopenHook); 113 | } else { 114 | // Android before 7.0 do not have linker namespace, so we can directly hook dlopen 115 | target = reinterpret_cast(dlopen); 116 | hook = reinterpret_cast(DlopenHook); 117 | } 118 | return DobbyHook(target, hook, &dlopen_backup) == RS_SUCCESS; 119 | }(); 120 | if (!initialized) [[unlikely]] return; 121 | auto library = env->GetStringUTFChars(lib, nullptr); 122 | entrypoints.emplace_back(library); 123 | env->ReleaseStringUTFChars(lib, library); 124 | } 125 | 126 | static const JNINativeMethod gMainNativeMethods[] = { 127 | {"recordNativeEntrypoint", "(Ljava/lang/String;)V", (void*) Main_recordNativeEntrypoint} 128 | }; 129 | 130 | void NativeHook::RegisterNatives(JNIEnv* env, jclass main) { 131 | env->RegisterNatives(main, gMainNativeMethods, NELEM(gMainNativeMethods)); 132 | } 133 | } // dreamland 134 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/top/canyie/dreamland/core/PackageMonitor.java: -------------------------------------------------------------------------------- 1 | package top.canyie.dreamland.core; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.ActivityThread; 5 | import android.content.BroadcastReceiver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.content.pm.ApplicationInfo; 10 | import android.content.pm.PackageManager; 11 | import android.os.Build; 12 | import android.os.Handler; 13 | import android.os.Looper; 14 | import android.os.Process; 15 | import android.os.RemoteException; 16 | import android.os.UserHandle; 17 | import android.os.UserHandleHidden; 18 | import android.util.Log; 19 | import java.lang.reflect.Method; 20 | 21 | import top.canyie.dreamland.ipc.DreamlandManagerService; 22 | import top.canyie.dreamland.utils.BuildUtils; 23 | 24 | /** 25 | * @author canyie 26 | */ 27 | public class PackageMonitor extends BroadcastReceiver { 28 | PackageMonitor() { 29 | } 30 | 31 | public static void startRegister() { 32 | try { 33 | ActivityThread activityThread = ActivityThread.currentActivityThread(); 34 | Context context = mirror.android.app.ActivityThread.REF.method("getSystemContext").call(activityThread); 35 | if (context == null) { 36 | Log.e(Dreamland.TAG, "No context for register package monitor"); 37 | return; 38 | } 39 | Handler h = new Handler(Looper.getMainLooper()); 40 | h.post(() -> new T(context).start()); 41 | } catch (Throwable e) { 42 | Log.e(Dreamland.TAG, "Cannot schedule register action", e); 43 | } 44 | } 45 | 46 | @Override public void onReceive(Context context, Intent intent) { 47 | String packageName = intent.getData().getSchemeSpecificPart(); 48 | String action = intent.getAction(); 49 | assert action != null; 50 | Log.i(Dreamland.TAG, "Received " + action + " for package " + packageName); 51 | DreamlandManagerService dm = DreamlandManagerService.getInstance(); 52 | ModuleManager moduleManager = dm.getModuleManager(); 53 | switch (action) { 54 | case Intent.ACTION_PACKAGE_REPLACED: 55 | if (!moduleManager.isModuleEnabled(packageName)) { 56 | // Not a module, or disabled. For disabled module, apk path will be updated in enable() 57 | return; 58 | } 59 | 60 | ApplicationInfo appInfo; 61 | String modulePath; 62 | try { 63 | appInfo = context.getPackageManager().getApplicationInfo(packageName, 0); 64 | modulePath = dm.getModulePath(appInfo); 65 | } catch (PackageManager.NameNotFoundException|RemoteException e) { 66 | Log.e(Dreamland.TAG, "getModulePath", e); 67 | return; 68 | } 69 | if (modulePath == null) { 70 | Log.e(Dreamland.TAG, "No valid apk found for module " + packageName); 71 | return; 72 | } 73 | moduleManager.updateModulePath(packageName, modulePath, appInfo.nativeLibraryDir); 74 | dm.clearModuleCache(); 75 | Log.i(Dreamland.TAG, "Updated module info for " + packageName); 76 | break; 77 | case Intent.ACTION_PACKAGE_FULLY_REMOVED: 78 | if (moduleManager.remove(packageName)) { 79 | dm.clearModuleCache(); 80 | Log.i(Dreamland.TAG, "Module " + packageName + " has been removed, clean up."); 81 | } 82 | break; 83 | } 84 | } 85 | 86 | static final class T extends Thread { 87 | private Context context; 88 | 89 | T (Context context) { 90 | super("Dreamland-PackageMonitor"); 91 | this.context = context; 92 | setDaemon(true); 93 | } 94 | 95 | @Override public void run() { 96 | Looper.prepare(); 97 | try { 98 | PackageMonitor monitor = new PackageMonitor(); 99 | Handler h = new Handler(Looper.myLooper()); 100 | 101 | IntentFilter intentFilter = new IntentFilter(); 102 | intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); 103 | intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); 104 | intentFilter.addDataScheme("package"); 105 | 106 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 107 | Method registerReceiverAsUser = Context.class.getDeclaredMethod("registerReceiverAsUser", 108 | BroadcastReceiver.class, UserHandle.class, IntentFilter.class, 109 | String.class, Handler.class, int.class); 110 | registerReceiverAsUser.setAccessible(true); 111 | registerReceiverAsUser.invoke(context, monitor, UserHandleHidden.ALL, 112 | intentFilter, null, h, Context.RECEIVER_NOT_EXPORTED); 113 | } else { 114 | @SuppressLint("DiscouragedPrivateApi") 115 | Method registerReceiverAsUser = Context.class.getDeclaredMethod("registerReceiverAsUser", 116 | BroadcastReceiver.class, UserHandle.class, IntentFilter.class, 117 | String.class, Handler.class); 118 | registerReceiverAsUser.setAccessible(true); 119 | registerReceiverAsUser.invoke(context, monitor, UserHandleHidden.ALL, 120 | intentFilter, null, h); 121 | } 122 | 123 | context = null; 124 | Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 125 | } catch (Throwable e) { 126 | Log.e(Dreamland.TAG, "Cannot register package monitor", e); 127 | return; 128 | } 129 | Looper.loop(); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/riru_flavor.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2022/2/28. 3 | // 4 | 5 | #include "dreamland.h" 6 | #include "flavor.h" 7 | #include "../riru.h" 8 | 9 | using namespace dreamland; 10 | 11 | static int riru_api_version = 0; 12 | static int* riru_allow_unload_ = nullptr; 13 | static bool requested_start_system_server_ = false; 14 | 15 | // Skip incomplete fork (post fork happens before pre fork) 16 | static bool skip_ = true; 17 | 18 | static void AllowUnload() { 19 | if (riru_allow_unload_) *riru_allow_unload_ = 1; 20 | } 21 | 22 | EXPORT void onModuleLoaded() { 23 | Flavor::OnModuleLoaded(true); 24 | } 25 | 26 | static void PostForkApp(JNIEnv* env) { 27 | bool allow_unload = skip_ || !Flavor::PostForkApp(env, requested_start_system_server_); 28 | if (allow_unload) 29 | AllowUnload(); 30 | } 31 | 32 | EXPORT int shouldSkipUid(int uid) { 33 | return Dreamland::ShouldSkipUid(uid) ? 1 : 0; 34 | } 35 | 36 | EXPORT void nativeForkAndSpecializePre(JNIEnv* env, jclass, jint* uid_ptr, jint*, 37 | jintArray*, jint*, jobjectArray*, jint*, jstring*, 38 | jstring*, jintArray*, jintArray*, 39 | jboolean* is_child_zygote, jstring*, jstring*, jboolean*, 40 | jobjectArray*) { 41 | if (skip_ = Flavor::ShouldSkip(*is_child_zygote, *uid_ptr); !skip_) { 42 | Flavor::PreFork(env, true); 43 | } 44 | } 45 | 46 | EXPORT int nativeForkAndSpecializePost(JNIEnv* env, jclass, jint result) { 47 | if (result == 0) PostForkApp(env); 48 | else skip_ = true; 49 | return 0; 50 | } 51 | 52 | EXPORT void nativeForkSystemServerPre(JNIEnv* env, jclass, uid_t*, gid_t*, 53 | jintArray*, jint*, jobjectArray*, jlong*, jlong*) { 54 | requested_start_system_server_ = true; 55 | // Only skip system server when we are disabled 56 | if (LIKELY(!Flavor::IsDisabled())) Flavor::PreFork(env, true); 57 | } 58 | 59 | EXPORT int nativeForkSystemServerPost(JNIEnv* env, jclass, jint result) { 60 | if (result == 0 && !Flavor::IsDisabled()) Flavor::PostForkSystemServer(env); 61 | return 0; 62 | } 63 | 64 | // ----------- Riru V22+ API ----------- 65 | 66 | static void forkAndSpecializePre( 67 | JNIEnv *env, jclass, jint *_uid, jint *gid, jintArray *gids, jint *runtimeFlags, 68 | jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName, 69 | jintArray *fdsToClose, jintArray *fdsToIgnore, jboolean *is_child_zygote, 70 | jstring *instructionSet, jstring *appDataDir, jboolean *isTopApp, jobjectArray *pkgDataInfoList, 71 | jobjectArray *whitelistedDataInfoList, jboolean *bindMountAppDataDirs, jboolean *bindMountAppStorageDirs) { 72 | if (skip_ = Flavor::ShouldSkip(*is_child_zygote, *_uid); !skip_) { 73 | Flavor::PreFork(env, true); 74 | } 75 | } 76 | 77 | static void forkAndSpecializePost(JNIEnv *env, jclass, jint res) { 78 | if (res == 0) PostForkApp(env); 79 | else skip_ = true; 80 | } 81 | 82 | static void specializeAppProcessPre( 83 | JNIEnv *env, jclass, jint *_uid, jint *gid, jintArray *gids, jint *runtimeFlags, 84 | jobjectArray *rlimits, jint *mountExternal, jstring *seInfo, jstring *niceName, 85 | jboolean *startChildZygote, jstring *instructionSet, jstring *appDataDir, 86 | jboolean *isTopApp, jobjectArray *pkgDataInfoList, jobjectArray *whitelistedDataInfoList, 87 | jboolean *bindMountAppDataDirs, jboolean *bindMountAppStorageDirs) { 88 | if (skip_ = Flavor::ShouldSkip(*startChildZygote, *_uid); !skip_) { 89 | Flavor::PreFork(env, false); 90 | } 91 | } 92 | 93 | static void specializeAppProcessPost(JNIEnv *env, jclass) { 94 | PostForkApp(env); 95 | } 96 | 97 | static void forkSystemServerPre( 98 | JNIEnv *env, jclass, uid_t *uid, gid_t *gid, jintArray *gids, jint *runtimeFlags, 99 | jobjectArray *rlimits, jlong *permittedCapabilities, jlong *effectiveCapabilities) { 100 | requested_start_system_server_ = true; 101 | // Only skip system server when we are disabled 102 | if (LIKELY(!Flavor::IsDisabled())) Flavor::PreFork(env, true); 103 | } 104 | 105 | static void forkSystemServerPost(JNIEnv *env, jclass, jint res) { 106 | if (res == 0 && !Flavor::IsDisabled()) Flavor::PostForkSystemServer(env); 107 | } 108 | 109 | static auto module = RiruVersionedModuleInfo { 110 | .moduleApiVersion = RIRU_NEW_MODULE_API_VERSION, 111 | .moduleInfo = RiruModuleInfo { 112 | .supportHide = true, 113 | .version = Dreamland::VERSION, 114 | .versionName = Dreamland::VERSION_NAME, 115 | .onModuleLoaded = onModuleLoaded, 116 | .shouldSkipUid = shouldSkipUid, 117 | .forkAndSpecializePre = forkAndSpecializePre, 118 | .forkAndSpecializePost = forkAndSpecializePost, 119 | .forkSystemServerPre = forkSystemServerPre, 120 | .forkSystemServerPost = forkSystemServerPost, 121 | .specializeAppProcessPre = specializeAppProcessPre, 122 | .specializeAppProcessPost = specializeAppProcessPost 123 | } 124 | }; 125 | 126 | static int step = 0; 127 | EXPORT void* init(Riru* arg) { 128 | step++; 129 | 130 | switch (step) { 131 | case 1: { 132 | int core_max_api_version = arg->riruApiVersion; 133 | riru_api_version = core_max_api_version <= RIRU_NEW_MODULE_API_VERSION ? core_max_api_version : RIRU_NEW_MODULE_API_VERSION; 134 | if (riru_api_version > 10 && riru_api_version < 25) { 135 | // V24 is pre-release version, not supported 136 | riru_api_version = 10; 137 | } 138 | if (riru_api_version >= 25) { 139 | module.moduleApiVersion = riru_api_version; 140 | riru_allow_unload_ = arg->allowUnload; 141 | return &module; 142 | } else { 143 | return &riru_api_version; 144 | } 145 | } 146 | case 2: { 147 | return &module.moduleInfo; 148 | } 149 | case 3: 150 | default: 151 | return nullptr; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /template/customize.sh: -------------------------------------------------------------------------------- 1 | SKIPUNZIP=1 2 | 3 | RIRU_OLD_PATH=/data/misc/riru 4 | RIRU_NEW_PATH=/data/adb/riru 5 | RIRU_MODULE_ID=dreamland 6 | DREAMLAND_PATH=/data/misc/dreamland 7 | RIRU_API=0 8 | 9 | ui_print "- Loading languages" 10 | unzip -o "$ZIPFILE" languages.sh -d "$TMPDIR" >&2 11 | [ -f "$TMPDIR/languages.sh" ] || abort "! Unable to extract languages.sh" 12 | . "$TMPDIR/languages.sh" 13 | 14 | if [ "$ARCH" != "arm64" ] && [ "$ARCH" != "arm" ]; then 15 | abort "! $ERR_UNSUPPORTED_ARCH $ARCH" 16 | else 17 | ui_print "- $ALERT_ARCH $ARCH" 18 | fi 19 | 20 | if [ "$API" -lt 21 ]; then 21 | abort "! $ERR_UNSUPPORTED_ANDROID_API $API" 22 | else 23 | ui_print "- $ALERT_ANDROID_API $API" 24 | [ "$API" -lt 24 ] && ui_print "- $WARN_OLD_ANDROID_API $API" 25 | fi 26 | 27 | if [ "$BOOTMODE" = "true" ]; then 28 | ui_print "- $ALERT_BOOTMODE" 29 | else 30 | ui_print "! $ERR_FLASH_FROM_RECOVERY_1" 31 | ui_print "! $ERR_FLASH_FROM_RECOVERY_2" 32 | abort "! $ERR_FLASH_FROM_RECOVERY_3" 33 | fi 34 | 35 | MAGISK_TMP=$(magisk --path) || MAGISK_TMP="/sbin" 36 | 37 | # "ZYGISK_ENABLED" is not an API but exported unexpectedly when installing from Magisk app 38 | # Magisk doesn't provide an API to detect if Zygisk is working, so the only way is... 39 | 40 | # Detect old legacy LD_PRELOAD zygisk 41 | [ -d "$MAGISK_TMP/.magisk/zygisk" ] && ZYGISK_ENABLED=1 42 | 43 | # Detect new native bridge based zygisk 44 | mount | grep -q libzygisk.so && ZYGISK_ENABLED=1 45 | 46 | # Detect Zygisk Next 47 | [ -d "/data/adb/modules/zygisksu" ] && ! [ -f "/data/adb/modules/zygisksu/disable" ] && ZYGISK_ENABLED=1 48 | 49 | if [ "$ZYGISK_ENABLED" = "1" ]; then 50 | [ "$MAGISK_VER_CODE" -lt 24000 ] && abort "! $ERR_ZYGISK_REQUIRES_24" 51 | FLAVOR="zygisk" 52 | ui_print "- $ALERT_ZYGISK_FLAVOR" 53 | else 54 | ui_print "- $ALERT_RIRU_FLAVOR" 55 | MAGISK_CURRENT_RIRU_MODULE_PATH="/data/adb/modules/riru-core" 56 | if [ -f $MAGISK_CURRENT_RIRU_MODULE_PATH/util_functions.sh ]; then 57 | # Riru V24+, api version is provided in util_functions.sh 58 | # I don't like this, but I can only follow this change 59 | RIRU_PATH=$MAGISK_CURRENT_RIRU_MODULE_PATH 60 | ui_print "- Load $MAGISK_CURRENT_RIRU_MODULE_PATH/util_functions.sh" 61 | # shellcheck disable=SC1090 62 | . $MAGISK_CURRENT_RIRU_MODULE_PATH/util_functions.sh 63 | 64 | # Pre Riru 25, as a old module 65 | if [ "$RIRU_API" -lt 25 ]; then 66 | ui_print "- Riru API version $RIRU_API is lower than v25" 67 | RIRU_PATH=$RIRU_NEW_PATH 68 | fi 69 | elif [ -f "$RIRU_OLD_PATH/api_version.new" ] || [ -f "$RIRU_OLD_PATH/api_version" ]; then 70 | RIRU_PATH="$RIRU_OLD_PATH" 71 | elif [ -f "$RIRU_NEW_PATH/api_version.new" ] || [ -f "$RIRU_NEW_PATH/api_version" ]; then 72 | RIRU_PATH="$RIRU_NEW_PATH" 73 | else 74 | abort "! $ERR_NO_FLAVOR" 75 | fi 76 | RIRU_MODULE_PATH="$RIRU_PATH/modules/$RIRU_MODULE_ID" 77 | 78 | [ "$RIRU_API" -ne 0 ] || RIRU_API=$(cat "$RIRU_PATH/api_version.new") || RIRU_API=$(cat "$RIRU_PATH/api_version") 79 | ui_print "- $ALERT_RIRU_API $RIRU_API" 80 | 81 | RIRU_MIN_API=$(grep_prop api "$TMPDIR/module.prop") 82 | [ "$RIRU_API" -ge "$RIRU_MIN_API" ] || abort "! $ERR_UNSUPPORTED_RIRU_API $RIRU_API" 83 | 84 | FLAVOR="riru" 85 | fi 86 | 87 | if [ "${BOOTMODE}" = "true" ]; then 88 | if [ "$(pm path 'top.canyie.dreamland.manager')" = "" ]; then 89 | if [ "$(pm path 'com.canyie.dreamland.manager')" != "" ]; then 90 | ui_print "- $WARN_OLD_MANAGER_1" 91 | ui_print "- $WARN_OLD_MANAGER_2" 92 | else 93 | ui_print "- $WARN_MANAGER_NOT_INSTALLED_1" 94 | ui_print "- $WARN_MANAGER_NOT_INSTALLED_2" 95 | fi 96 | ui_print "- $WARN_PLEASE_INSTALL_NEW_MANAGER" 97 | fi 98 | fi 99 | 100 | ui_print "- $ALERT_EXTRACT_MODULE_FILES" 101 | unzip -o "$ZIPFILE" module.prop uninstall.sh post-fs-data.sh service.sh sepolicy.rule system.prop -d "$MODPATH" >&2 || abort "! $ERR_EXTRACT_MODULE_FILES $?" 102 | unzip -o "$ZIPFILE" 'dreamland.jar' 'riru/*' -d "$MODPATH" >&2 || abort "! $ERR_EXTRACT_SYSTEM_FOLDER $?" 103 | 104 | # Remove broken file created by broken builds 105 | [ -f "$DREAMLAND_PATH" ] && rm "$DREAMLAND_PATH" 106 | 107 | if [ "$IS64BIT" = "false" ]; then 108 | ui_print "- $ALERT_REMOVE_LIB64" 109 | rm -rf "$MODPATH/riru/lib64" 110 | fi 111 | 112 | ui_print "- $ALERT_FLAVOR_SPECIFC" 113 | if [ "$FLAVOR" = "riru" ]; then 114 | if [ "$RIRU_API" -lt 25 ]; then 115 | ui_print "- $ALERT_OLD_RIRU $RIRU_API" 116 | mkdir "$MODPATH/system/" 117 | mv -f "$MODPATH/riru/lib" "$MODPATH/system/" 118 | [ -d "$MODPATH/riru/lib64" ] && mv -f "$MODPATH/riru/lib64" "$MODPATH/system/" 2>&1 119 | rm -rf "$MODPATH/riru" 120 | [ -d $RIRU_MODULE_PATH ] || mkdir -p $RIRU_MODULE_PATH || abort "! Can't create $RIRU_MODULE_PATH: $?" 121 | cp -f "$MODPATH/module.prop" "$RIRU_MODULE_PATH/module.prop" 122 | else 123 | # Riru v25+, user may upgrade from old module without uninstall 124 | # Remove the Riru v22's module path to make sure riru knows we're a new module 125 | RIRU_22_MODULE_PATH="$RIRU_NEW_PATH/modules/$RIRU_MODULE_ID" 126 | ui_print "- $ALERT_REMOVE_OLD_FOR_NEW_RIRU" 127 | rm -rf "$RIRU_22_MODULE_PATH" 128 | fi 129 | else 130 | mkdir -p "$MODPATH/zygisk" 131 | mv -f "$MODPATH/riru/lib/libriru_dreamland.so" "$MODPATH/zygisk/armeabi-v7a.so" || abort "! $ERR_FLAVOR_SPECIFC" 132 | [ -f "$MODPATH/riru/lib64/libriru_dreamland.so" ] && (mv -f "$MODPATH/riru/lib64/libriru_dreamland.so" "$MODPATH/zygisk/arm64-v8a.so" || abort "! $ERR_FLAVOR_SPECIFC") 133 | 134 | # Magisk won't load Riru modules if Zygisk enabled 135 | rm -rf "$MODPATH/riru" || abort "! $ERR_FLAVOR_SPECIFC" 136 | fi 137 | 138 | ui_print "- $ALERT_PREPARE_LOCAL_DIR" 139 | [ -d "$DREAMLAND_PATH" ] || mkdir -p "$DREAMLAND_PATH" || abort "! $ERR_PREPARE_LOCAL_DIR $?" 140 | mv -f "$MODPATH/dreamland.jar" "$DREAMLAND_PATH" || abort "! $ERR_EXTRACT_SYSTEM_FOLDER $?" 141 | 142 | if [ "$MAGISK_VER_CODE" -lt 20200 ]; then 143 | ui_print "- $ALRET_REMOVE_SEPOLICY_1" 144 | ui_print "- $ALRET_REMOVE_SEPOLICY_2" 145 | rm -f "$MODPATH/sepolicy.rule" 146 | fi 147 | 148 | # before Magisk 16e4c67, sepolicy.rule is copied on the second reboot 149 | if [ "$MAGISK_VER_CODE" -lt 21006 ]; then 150 | ui_print "- $ALERT_REBOOT_TWICE_1" 151 | ui_print "- $ALERT_REBOOT_TWICE_2" 152 | fi 153 | 154 | ui_print "- $ALERT_SETTING_PERMISSIONS" 155 | # The following is the default rule, DO NOT remove 156 | set_perm_recursive "$MODPATH" 0 0 0755 0644 157 | set_perm_recursive "$DREAMLAND_PATH" 1000 1000 0700 0600 u:object_r:system_data_file:s0 158 | -------------------------------------------------------------------------------- /hiddenapi-stubs/src/main/java/android/content/res/ResourcesHidden.java: -------------------------------------------------------------------------------- 1 | package android.content.res; 2 | 3 | import android.annotation.TargetApi; 4 | import android.graphics.Movie; 5 | import android.graphics.drawable.Drawable; 6 | import android.os.Build; 7 | import android.util.DisplayMetrics; 8 | import android.util.TypedValue; 9 | 10 | import java.io.InputStream; 11 | 12 | import dev.rikka.tools.refine.RefineAs; 13 | 14 | @RefineAs(Resources.class) 15 | public class ResourcesHidden { 16 | @SuppressWarnings("serial") 17 | public static class NotFoundException extends RuntimeException { 18 | public NotFoundException() { 19 | } 20 | 21 | public NotFoundException(String name) { 22 | throw new UnsupportedOperationException("Stub!"); 23 | } 24 | } 25 | 26 | public final class Theme { 27 | } 28 | 29 | public ResourcesHidden(ClassLoader classLoader) { 30 | throw new UnsupportedOperationException("Stub!"); 31 | } 32 | 33 | public ResourcesHidden(AssetManagerHidden assets, DisplayMetrics metrics, Configuration config) { 34 | throw new UnsupportedOperationException("Stub!"); 35 | } 36 | 37 | public static Resources getSystem() { 38 | throw new UnsupportedOperationException("Stub!"); 39 | } 40 | 41 | public XmlResourceParser getAnimation(int id) throws NotFoundException { 42 | throw new UnsupportedOperationException("Stub!"); 43 | } 44 | 45 | public final AssetManager getAssets() { 46 | throw new UnsupportedOperationException("Stub!"); 47 | } 48 | 49 | public boolean getBoolean(int id) throws NotFoundException { 50 | throw new UnsupportedOperationException("Stub!"); 51 | } 52 | 53 | public int getColor(int id) throws NotFoundException { 54 | throw new UnsupportedOperationException("Stub!"); 55 | } 56 | 57 | public ColorStateList getColorStateList(int id) throws NotFoundException { 58 | throw new UnsupportedOperationException("Stub!"); 59 | } 60 | 61 | public Configuration getConfiguration() { 62 | throw new UnsupportedOperationException("Stub!"); 63 | } 64 | 65 | public float getDimension(int id) throws NotFoundException { 66 | throw new UnsupportedOperationException("Stub!"); 67 | } 68 | 69 | public int getDimensionPixelOffset(int id) throws NotFoundException { 70 | throw new UnsupportedOperationException("Stub!"); 71 | } 72 | 73 | public int getDimensionPixelSize(int id) throws NotFoundException { 74 | throw new UnsupportedOperationException("Stub!"); 75 | } 76 | 77 | public DisplayMetrics getDisplayMetrics() { 78 | throw new UnsupportedOperationException("Stub!"); 79 | } 80 | 81 | public Drawable getDrawable(int id) throws NotFoundException { 82 | throw new UnsupportedOperationException("Stub!"); 83 | } 84 | 85 | /** Since SDK21 */ 86 | public Drawable getDrawable(int id, Theme theme) throws NotFoundException { 87 | throw new UnsupportedOperationException("Stub!"); 88 | } 89 | 90 | /** Since SDK21, CM12 */ 91 | public Drawable getDrawable(int id, Theme theme, boolean supportComposedIcons) throws NotFoundException { 92 | throw new UnsupportedOperationException("Stub!"); 93 | } 94 | 95 | public Drawable getDrawableForDensity(int id, int density) throws NotFoundException { 96 | throw new UnsupportedOperationException("Stub!"); 97 | } 98 | 99 | /** Since SDK21 */ 100 | public Drawable getDrawableForDensity(int id, int density, Theme theme) { 101 | throw new UnsupportedOperationException("Stub!"); 102 | } 103 | 104 | /** Since SDK21, CM12 */ 105 | public Drawable getDrawableForDensity(int id, int density, Theme theme, boolean supportComposedIcons) { 106 | throw new UnsupportedOperationException("Stub!"); 107 | } 108 | 109 | /** Since SDK21 */ 110 | public float getFloat(int id) { 111 | throw new UnsupportedOperationException("Stub!"); 112 | } 113 | 114 | public float getFraction(int id, int base, int pbase) { 115 | throw new UnsupportedOperationException("Stub!"); 116 | } 117 | 118 | public int getIdentifier(String name, String defType, String defPackage) { 119 | throw new UnsupportedOperationException("Stub!"); 120 | } 121 | 122 | public int[] getIntArray(int id) throws NotFoundException { 123 | throw new UnsupportedOperationException("Stub!"); 124 | } 125 | 126 | public int getInteger(int id) throws NotFoundException { 127 | throw new UnsupportedOperationException("Stub!"); 128 | } 129 | 130 | public XmlResourceParser getLayout(int id) throws NotFoundException { 131 | throw new UnsupportedOperationException("Stub!"); 132 | } 133 | 134 | public Movie getMovie(int id) throws NotFoundException { 135 | throw new UnsupportedOperationException("Stub!"); 136 | } 137 | 138 | public String getQuantityString(int id, int quantity) throws NotFoundException { 139 | throw new UnsupportedOperationException("Stub!"); 140 | } 141 | 142 | public String getQuantityString(int id, int quantity, Object... formatArgs) { 143 | throw new UnsupportedOperationException("Stub!"); 144 | } 145 | 146 | public CharSequence getQuantityText(int id, int quantity) throws NotFoundException { 147 | throw new UnsupportedOperationException("Stub!"); 148 | } 149 | 150 | public String getResourceEntryName(int resid) throws NotFoundException { 151 | throw new UnsupportedOperationException("Stub!"); 152 | } 153 | 154 | public String getResourceName(int resid) throws NotFoundException { 155 | throw new UnsupportedOperationException("Stub!"); 156 | } 157 | 158 | public String getResourcePackageName(int resid) throws NotFoundException { 159 | throw new UnsupportedOperationException("Stub!"); 160 | } 161 | 162 | public String getResourceTypeName(int resid) throws NotFoundException { 163 | throw new UnsupportedOperationException("Stub!"); 164 | } 165 | 166 | public String getString(int id) throws NotFoundException { 167 | throw new UnsupportedOperationException("Stub!"); 168 | } 169 | 170 | public String getString(int id, Object... formatArgs) throws NotFoundException { 171 | throw new UnsupportedOperationException("Stub!"); 172 | } 173 | 174 | public String[] getStringArray(int id) throws NotFoundException { 175 | throw new UnsupportedOperationException("Stub!"); 176 | } 177 | 178 | public CharSequence getText(int id) throws NotFoundException { 179 | throw new UnsupportedOperationException("Stub!"); 180 | } 181 | 182 | public CharSequence getText(int id, CharSequence def) { 183 | throw new UnsupportedOperationException("Stub!"); 184 | } 185 | 186 | public CharSequence[] getTextArray(int id) throws NotFoundException { 187 | throw new UnsupportedOperationException("Stub!"); 188 | } 189 | 190 | public void getValue(int id, TypedValue outValue, boolean resolveRefs) { 191 | throw new UnsupportedOperationException("Stub!"); 192 | } 193 | 194 | public XmlResourceParser getXml(int id) throws NotFoundException { 195 | throw new UnsupportedOperationException("Stub!"); 196 | } 197 | 198 | public InputStream openRawResource(int id) throws NotFoundException { 199 | throw new UnsupportedOperationException("Stub!"); 200 | } 201 | 202 | public TypedArray obtainTypedArray(int id) { 203 | throw new UnsupportedOperationException("Stub!"); 204 | } 205 | 206 | public ClassLoader getClassLoader() { 207 | throw new UnsupportedOperationException("Stub!"); 208 | } 209 | 210 | @TargetApi(Build.VERSION_CODES.N) public ResourcesImpl getImpl() { 211 | throw new UnsupportedOperationException("Stub!"); 212 | } 213 | 214 | @TargetApi(Build.VERSION_CODES.N) public void setImpl(ResourcesImpl impl) { 215 | throw new UnsupportedOperationException("Stub!"); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /app/src/main/cpp/dreamland/resources_hook.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by canyie on 2020/8/5. 3 | // Code from https://github.com/ElderDrivers/EdXposed/blob/master/edxp-core/src/main/cpp/main/src/resource_hook.cpp 4 | // 5 | 6 | #include "resources_hook.h" 7 | #include "../utils/byte_order.h" 8 | #include "../utils/scoped_elf.h" 9 | #include "../utils/log.h" 10 | #include "../utils/scoped_local_ref.h" 11 | #include "../utils/jni_helper.h" 12 | 13 | using namespace dreamland; 14 | 15 | int32_t (*ResourcesHook::ResXMLParser_next)(void*) = nullptr; 16 | int32_t (*ResourcesHook::ResXMLParser_restart)(void*) = nullptr; 17 | int32_t (*ResourcesHook::ResXMLParser_getAttributeNameID)(void*, int) = nullptr; 18 | char16_t* (*ResourcesHook::ResStringPool_stringAt)(const void*, int32_t, size_t*) = nullptr; 19 | android::expected (*ResourcesHook::ResStringPool_stringAtS)( 20 | const void*, size_t) = nullptr; 21 | jclass ResourcesHook::XResources = nullptr; 22 | jmethodID ResourcesHook::translateResId = nullptr; 23 | jmethodID ResourcesHook::translateAttrId = nullptr; 24 | 25 | void ResourcesHook::JNI_rewriteXmlReferencesNative(JNIEnv *env, jclass, 26 | jlong parserPtr, jobject origRes, jobject repRes) { 27 | auto parser = (android::ResXMLParser*) parserPtr; 28 | 29 | if (parser == nullptr) 30 | return; 31 | 32 | const android::ResXMLTree& mTree = parser->mTree; 33 | auto mResIds = (uint32_t*) mTree.mResIds; 34 | android::ResXMLTree_attrExt* tag; 35 | int attrCount; 36 | 37 | do { 38 | switch (ResXMLParser_next(parser)) { 39 | case android::ResXMLParser::START_TAG: 40 | tag = (android::ResXMLTree_attrExt*) parser->mCurExt; 41 | attrCount = dtohs(tag->attributeCount); 42 | for (int idx = 0; idx < attrCount; idx++) { 43 | auto attr = (android::ResXMLTree_attribute*) 44 | (((const uint8_t*) tag) 45 | + dtohs(tag->attributeStart) 46 | + (dtohs(tag->attributeSize) * idx)); 47 | 48 | // find resource IDs for attribute names 49 | int32_t attrNameID = ResXMLParser_getAttributeNameID(parser, idx); 50 | // only replace attribute name IDs for app packages 51 | if (attrNameID >= 0 && (size_t) attrNameID < mTree.mNumResIds && 52 | dtohl(mResIds[attrNameID]) >= 0x7f000000) { 53 | size_t attrNameLen; 54 | const char16_t* attrName; 55 | if (ResStringPool_stringAt) { 56 | attrName = ResStringPool_stringAt(&(mTree.mStrings), attrNameID, &attrNameLen); 57 | } else { 58 | auto s = ResStringPool_stringAtS(&(mTree.mStrings), attrNameID); 59 | attrName = s->data_; 60 | attrNameLen = s->length_; 61 | } 62 | jint attrResID = env->CallStaticIntMethod(XResources, translateAttrId, 63 | env->NewString((const jchar*) attrName, attrNameLen), origRes); 64 | if (UNLIKELY(env->ExceptionCheck())) 65 | goto leave; 66 | 67 | mResIds[attrNameID] = htodl(attrResID); 68 | } 69 | 70 | // find original resource IDs for reference values (app packages only) 71 | if (attr->typedValue.dataType != android::Res_value::TYPE_REFERENCE) 72 | continue; 73 | 74 | jint oldValue = dtohl(attr->typedValue.data); 75 | if (oldValue < 0x7f000000) 76 | continue; 77 | 78 | jint newValue = env->CallStaticIntMethod(XResources, translateResId, 79 | oldValue, origRes, repRes); 80 | if (UNLIKELY(env->ExceptionCheck())) 81 | goto leave; 82 | 83 | if (newValue != oldValue) 84 | attr->typedValue.data = htodl(newValue); 85 | } 86 | continue; 87 | case android::ResXMLParser::END_DOCUMENT: 88 | case android::ResXMLParser::BAD_DOCUMENT: 89 | goto leave; 90 | default: 91 | continue; 92 | } 93 | } while (true); 94 | 95 | leave: 96 | ResXMLParser_restart(parser); 97 | } 98 | 99 | static constexpr const JNINativeMethod gMethods[] = { 100 | {"rewriteXmlReferencesNative", "(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V", (void*) ResourcesHook::JNI_rewriteXmlReferencesNative} 101 | }; 102 | 103 | bool ResourcesHook::Init(JNIEnv* env, jobject classLoader) { 104 | ScopedElf handle("libandroidfw.so"); 105 | 106 | #define FIND_SYMBOL(symbol, out) handle.GetSymbolAddress((symbol), reinterpret_cast(&(out))) 107 | 108 | #define FIND_SYMBOL_OR_FAIL(symbol, out) \ 109 | if (UNLIKELY(!FIND_SYMBOL((symbol), (out)))) { \ 110 | LOGE("Resources hook: could not find symbol %s", (symbol));\ 111 | return false;\ 112 | } 113 | 114 | FIND_SYMBOL_OR_FAIL("_ZN7android12ResXMLParser4nextEv", ResXMLParser_next); 115 | FIND_SYMBOL_OR_FAIL("_ZN7android12ResXMLParser7restartEv", ResXMLParser_restart); 116 | FIND_SYMBOL_OR_FAIL(LP_SELECT("_ZNK7android12ResXMLParser18getAttributeNameIDEm", 117 | "_ZNK7android12ResXMLParser18getAttributeNameIDEj"), ResXMLParser_getAttributeNameID); 118 | if (UNLIKELY(!FIND_SYMBOL(LP_SELECT("_ZNK7android13ResStringPool8stringAtEmPm","_ZNK7android13ResStringPool8stringAtEjPj"), ResStringPool_stringAt))) { 119 | // Android S 120 | FIND_SYMBOL_OR_FAIL(LP_SELECT("_ZNK7android13ResStringPool8stringAtEm", "_ZNK7android13ResStringPool8stringAtEj"), ResStringPool_stringAtS); 121 | } 122 | 123 | #undef FIND_SYMBOL_OR_FAIL 124 | #undef FIND_SYMBOL 125 | 126 | ScopedLocalRef localXResources(env, JNIHelper::FindClassFromClassLoader(env, 127 | kXResourcesClassName, classLoader)); 128 | if (UNLIKELY(localXResources.IsNull())) { 129 | LOGE("Resources hook: could not find class XResources"); 130 | return false; 131 | } 132 | 133 | if (UNLIKELY(env->RegisterNatives(localXResources.Get(), gMethods, NELEM(gMethods)) != JNI_OK)) { 134 | LOGE("Resources hook: could not register native methods for class XResources"); 135 | return false; 136 | } 137 | 138 | translateAttrId = env->GetStaticMethodID(localXResources.Get(), "translateAttrId", 139 | "(Ljava/lang/String;Landroid/content/res/XResources;)I"); 140 | if (UNLIKELY(translateAttrId == nullptr)) { 141 | LOGE("Resources hook: could not find method translateAttrId on class XResources"); 142 | return false; 143 | } 144 | 145 | translateResId = env->GetStaticMethodID(localXResources.Get(), "translateResId", 146 | "(ILandroid/content/res/XResources;Landroid/content/res/Resources;)I"); 147 | if (UNLIKELY(translateResId == nullptr)) { 148 | LOGE("Resources hook: could not find method translateResId on class XResources"); 149 | return false; 150 | } 151 | 152 | XResources = static_cast(env->NewGlobalRef(localXResources.Get())); 153 | if (UNLIKELY(XResources == nullptr)) { 154 | LOGE("Resources hook: could not create global reference for class XResources."); 155 | return false; 156 | } 157 | return true; 158 | } 159 | --------------------------------------------------------------------------------