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 | # 梦境框架 [](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 extends E> 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 [](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 |
--------------------------------------------------------------------------------