diff --git a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/config/BaseHookProvider.java b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/config/BaseHookProvider.java index 5f7fd05e..019c13c9 100644 --- a/edxp-common/src/main/java/com/elderdrivers/riru/edxp/config/BaseHookProvider.java +++ b/edxp-common/src/main/java/com/elderdrivers/riru/edxp/config/BaseHookProvider.java @@ -1,5 +1,8 @@ package com.elderdrivers.riru.edxp.config; +import android.content.res.Resources; +import android.content.res.XResources; + import com.elderdrivers.riru.edxp.deopt.PrebuiltMethodsDeopter; import com.elderdrivers.riru.edxp.hook.HookProvider; @@ -32,4 +35,14 @@ public abstract class BaseHookProvider implements HookProvider { public void deoptMethodNative(Object method) { } + + @Override + public boolean initXResourcesNative() { + return false; + } + + @Override + public void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes) { + + } } diff --git a/edxp-core/build.gradle b/edxp-core/build.gradle index 0b8ab926..cf759492 100644 --- a/edxp-core/build.gradle +++ b/edxp-core/build.gradle @@ -2,7 +2,7 @@ import org.gradle.internal.os.OperatingSystem apply plugin: 'com.android.library' -version "v0.3.1.8_beta-SNAPSHOT" +version "v0.4.0.1_beta-SNAPSHOT" ext { module_name = "EdXposed" diff --git a/edxp-core/edconfig.tpl b/edxp-core/edconfig.tpl index 5fe6d093..95f840e1 100644 --- a/edxp-core/edconfig.tpl +++ b/edxp-core/edconfig.tpl @@ -1,4 +1,4 @@ -version=90.0-0.3.1.8-beta-SNAPSHOT ($backend) +version=90.0-0.4.0.1-beta-SNAPSHOT ($backend) arch=arm64 minsdk=23 maxsdk=28 diff --git a/edxp-core/jni/main/Android.mk b/edxp-core/jni/main/Android.mk index 08325358..55f0035a 100644 --- a/edxp-core/jni/main/Android.mk +++ b/edxp-core/jni/main/Android.mk @@ -13,6 +13,7 @@ LOCAL_LDFLAGS := -Wl LOCAL_SRC_FILES:= \ main.cpp \ native_hook/native_hook.cpp \ + native_hook/resource_hook.cpp \ native_hook/riru_hook.cpp \ include/misc.cpp \ include/riru.c \ diff --git a/edxp-core/jni/main/include/ByteOrder.h b/edxp-core/jni/main/include/ByteOrder.h new file mode 100644 index 00000000..d44ef43e --- /dev/null +++ b/edxp-core/jni/main/include/ByteOrder.h @@ -0,0 +1,43 @@ +// +// Created by solo on 2019/3/24. +// + +#ifndef EDXPOSED_TEMP_BYTEORDER_H +#define EDXPOSED_TEMP_BYTEORDER_H + +#include + +static inline uint32_t android_swap_long(uint32_t v) +{ + return (v<<24) | ((v<<8)&0x00FF0000) | ((v>>8)&0x0000FF00) | (v>>24); +} +static inline uint16_t android_swap_short(uint16_t v) +{ + return (v<<8) | (v>>8); +} + +#define DEVICE_BYTE_ORDER LITTLE_ENDIAN +#if BYTE_ORDER == DEVICE_BYTE_ORDER +#define dtohl(x) (x) +#define dtohs(x) (x) +#define htodl(x) (x) +#define htods(x) (x) +#else +#define dtohl(x) (android_swap_long(x)) +#define dtohs(x) (android_swap_short(x)) +#define htodl(x) (android_swap_long(x)) +#define htods(x) (android_swap_short(x)) +#endif +#if BYTE_ORDER == LITTLE_ENDIAN +#define fromlel(x) (x) +#define fromles(x) (x) +#define tolel(x) (x) +#define toles(x) (x) +#else +#define fromlel(x) (android_swap_long(x)) +#define fromles(x) (android_swap_short(x)) +#define tolel(x) (android_swap_long(x)) +#define toles(x) (android_swap_short(x)) +#endif + +#endif //EDXPOSED_TEMP_BYTEORDER_H diff --git a/edxp-core/jni/main/java_hook/java_hook.cpp b/edxp-core/jni/main/java_hook/java_hook.cpp index 2e9194ec..19852660 100644 --- a/edxp-core/jni/main/java_hook/java_hook.cpp +++ b/edxp-core/jni/main/java_hook/java_hook.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "java_hook/java_hook.h" #include "include/logging.h" #include "include/fd_utils-inl.h" @@ -125,6 +126,12 @@ static JNINativeMethod hookMethods[] = { }, { "waitForGcToComplete", "(J)I", (void *) waitForGcToComplete + }, + { + "initXResourcesNative", "()Z", (void *) XposedBridge_initXResourcesNative + }, + { + "rewriteXmlReferencesNative", "(JLandroid/content/res/XResources;Landroid/content/res/Resources;)V", (void *) XResources_rewriteXmlReferencesNative } }; @@ -162,7 +169,7 @@ void loadDexAndInit(JNIEnv *env, const char *dexPath) { jclass entry_class = findClassFromLoader(env, myClassLoader, ENTRY_CLASS_NAME); if (NULL != entry_class) { LOGD("HookEntry Class %p", entry_class); - env->RegisterNatives(entry_class, hookMethods, 15); + env->RegisterNatives(entry_class, hookMethods, NELEM(hookMethods)); isInited = true; LOGD("RegisterNatives succeed for HookEntry."); } else { diff --git a/edxp-core/jni/main/java_hook/java_hook.h b/edxp-core/jni/main/java_hook/java_hook.h index ac6e2939..9e678baf 100644 --- a/edxp-core/jni/main/java_hook/java_hook.h +++ b/edxp-core/jni/main/java_hook/java_hook.h @@ -4,6 +4,8 @@ #include #include +#define NELEM(x) (sizeof(x)/sizeof((x)[0])) + extern jobject gInjectDexClassLoader; void loadDexAndInit(JNIEnv *env, const char *dexPath); diff --git a/edxp-core/jni/main/native_hook/resource_hook.cpp b/edxp-core/jni/main/native_hook/resource_hook.cpp new file mode 100644 index 00000000..09282f92 --- /dev/null +++ b/edxp-core/jni/main/native_hook/resource_hook.cpp @@ -0,0 +1,169 @@ +// +// Created by solo on 2019/3/24. +// + +#include +#include +#include +#include +#include "resource_hook.h" + +#define CLASS_XRESOURCES "android/content/res/XResources" + +jclass classXResources; +jmethodID methodXResourcesTranslateAttrId; +jmethodID methodXResourcesTranslateResId; + +int32_t (*ResXMLParser_next)(void *); + +void (*ResXMLParser_restart)(void *); + +int32_t (*ResXMLParser_getAttributeNameID)(void *, int); + +char16_t *(*ResStringPool_stringAt)(const void *, int32_t, size_t *); + +bool prepareSymbols() { + void *fwHandle = dlopen(kLibFwPath, RTLD_LAZY | RTLD_GLOBAL); + if (!fwHandle) { + LOGE("can't open libandroidfw: %s", dlerror()); + return false; + } + ResXMLParser_next = reinterpret_cast(dlsym(fwHandle, + "_ZN7android12ResXMLParser4nextEv")); + if (!ResXMLParser_next) { + LOGE("can't get ResXMLParser_next: %s", dlerror()); + return false; + } + ResXMLParser_restart = reinterpret_cast(dlsym(fwHandle, + "_ZN7android12ResXMLParser7restartEv")); + if (!ResXMLParser_restart) { + LOGE("can't get ResXMLParser_restart: %s", dlerror()); + return false; + } + ResXMLParser_getAttributeNameID = reinterpret_cast(dlsym(fwHandle, +#if defined(__LP64__) + "_ZNK7android12ResXMLParser18getAttributeNameIDEm" +#else + "_ZNK7android12ResXMLParser18getAttributeNameIDEj" +#endif + )); + if (!ResXMLParser_getAttributeNameID) { + LOGE("can't get ResXMLParser_getAttributeNameID: %s", dlerror()); + return false; + } + ResStringPool_stringAt = reinterpret_cast(dlsym( + fwHandle, +#if defined(__LP64__) + "_ZNK7android13ResStringPool8stringAtEmPm" +#else + "_ZNK7android13ResStringPool8stringAtEjPj" +#endif + )); + if (!ResStringPool_stringAt) { + LOGE("can't get ResStringPool_stringAt: %s", dlerror()); + return false; + } + return true; +} + +jboolean XposedBridge_initXResourcesNative(JNIEnv *env, jclass) { + classXResources = env->FindClass(CLASS_XRESOURCES); + if (classXResources == NULL) { + LOGE("Error while loading XResources class '%s':", CLASS_XRESOURCES); + env->ExceptionClear(); + return false; + } + classXResources = reinterpret_cast(env->NewGlobalRef(classXResources)); + + methodXResourcesTranslateResId = env->GetStaticMethodID(classXResources, "translateResId", + "(ILandroid/content/res/XResources;Landroid/content/res/Resources;)I"); + if (methodXResourcesTranslateResId == NULL) { + LOGE("ERROR: could not find method %s.translateResId(int, XResources, Resources)", + CLASS_XRESOURCES); + env->ExceptionClear(); + return false; + } + + methodXResourcesTranslateAttrId = env->GetStaticMethodID(classXResources, "translateAttrId", + "(Ljava/lang/String;Landroid/content/res/XResources;)I"); + if (methodXResourcesTranslateAttrId == NULL) { + LOGE("ERROR: could not find method %s.findAttrId(String, XResources)", CLASS_XRESOURCES); + env->ExceptionClear(); + return false; + } + + return prepareSymbols(); +} + +void XResources_rewriteXmlReferencesNative(JNIEnv *env, jclass, + jlong parserPtr, jobject origRes, jobject repRes) { + + ResXMLParser *parser = (ResXMLParser *) parserPtr; + + if (parser == nullptr) + return; + + const ResXMLTree &mTree = parser->mTree; + uint32_t *mResIds = (uint32_t *) mTree.mResIds; + ResXMLTree_attrExt *tag; + int attrCount; + + do { + switch (ResXMLParser_next(parser)) { + case ResXMLParser::START_TAG: + tag = (ResXMLTree_attrExt *) parser->mCurExt; + attrCount = dtohs(tag->attributeCount); + for (int idx = 0; idx < attrCount; idx++) { + ResXMLTree_attribute *attr = (ResXMLTree_attribute *) + (((const uint8_t *) tag) + + dtohs(tag->attributeStart) + + (dtohs(tag->attributeSize) * idx)); + + // find resource IDs for attribute names + int32_t attrNameID = ResXMLParser_getAttributeNameID(parser, idx); + // only replace attribute name IDs for app packages + if (attrNameID >= 0 && (size_t) attrNameID < mTree.mNumResIds && + dtohl(mResIds[attrNameID]) >= 0x7f000000) { + size_t attNameLen; + const char16_t *attrName = ResStringPool_stringAt(&(mTree.mStrings), + attrNameID, &attNameLen); + jint attrResID = env->CallStaticIntMethod(classXResources, + methodXResourcesTranslateAttrId, + env->NewString( + (const jchar *) attrName, + attNameLen), origRes); + if (env->ExceptionCheck()) + goto leave; + + mResIds[attrNameID] = htodl(attrResID); + } + + // find original resource IDs for reference values (app packages only) + if (attr->typedValue.dataType != Res_value::TYPE_REFERENCE) + continue; + + jint oldValue = dtohl(attr->typedValue.data); + if (oldValue < 0x7f000000) + continue; + + jint newValue = env->CallStaticIntMethod(classXResources, + methodXResourcesTranslateResId, + oldValue, origRes, repRes); + if (env->ExceptionCheck()) + goto leave; + + if (newValue != oldValue) + attr->typedValue.data = htodl(newValue); + } + continue; + case ResXMLParser::END_DOCUMENT: + case ResXMLParser::BAD_DOCUMENT: + goto leave; + default: + continue; + } + } while (true); + + leave: + ResXMLParser_restart(parser); +} \ No newline at end of file diff --git a/edxp-core/jni/main/native_hook/resource_hook.h b/edxp-core/jni/main/native_hook/resource_hook.h new file mode 100644 index 00000000..8208f302 --- /dev/null +++ b/edxp-core/jni/main/native_hook/resource_hook.h @@ -0,0 +1,276 @@ +// +// Created by solo on 2019/3/24. +// + +#ifndef EDXPOSED_TEMP_RESOURCE_HOOK_H +#define EDXPOSED_TEMP_RESOURCE_HOOK_H + +#include + +#if defined(__LP64__) +static constexpr const char *kLibFwPath = "/system/lib64/libandroidfw.so"; +#else +static constexpr const char *kLibFwPath = "/system/lib/libandroidfw.so"; +#endif + +jboolean XposedBridge_initXResourcesNative(JNIEnv *env, jclass); + +void XResources_rewriteXmlReferencesNative(JNIEnv *env, jclass, + jlong parserPtr, jobject origRes, jobject repRes); + +typedef int32_t status_t; + +enum { + RES_NULL_TYPE = 0x0000, + RES_STRING_POOL_TYPE = 0x0001, + RES_TABLE_TYPE = 0x0002, + RES_XML_TYPE = 0x0003, + // Chunk types in RES_XML_TYPE + RES_XML_FIRST_CHUNK_TYPE = 0x0100, + RES_XML_START_NAMESPACE_TYPE = 0x0100, + RES_XML_END_NAMESPACE_TYPE = 0x0101, + RES_XML_START_ELEMENT_TYPE = 0x0102, + RES_XML_END_ELEMENT_TYPE = 0x0103, + RES_XML_CDATA_TYPE = 0x0104, + RES_XML_LAST_CHUNK_TYPE = 0x017f, + // This contains a uint32_t array mapping strings in the string + // pool back to resource identifiers. It is optional. + RES_XML_RESOURCE_MAP_TYPE = 0x0180, + // Chunk types in RES_TABLE_TYPE + RES_TABLE_PACKAGE_TYPE = 0x0200, + RES_TABLE_TYPE_TYPE = 0x0201, + RES_TABLE_TYPE_SPEC_TYPE = 0x0202, + RES_TABLE_LIBRARY_TYPE = 0x0203 +}; + +struct ResXMLTree_node { + void *header; + // Line number in original source file at which this element appeared. + uint32_t lineNumber; + // Optional XML comment that was associated with this element; -1 if none. + void *comment; +}; + +class ResXMLTree; + +class ResXMLParser { + +public: + enum event_code_t { + BAD_DOCUMENT = -1, + START_DOCUMENT = 0, + END_DOCUMENT = 1, + + FIRST_CHUNK_CODE = RES_XML_FIRST_CHUNK_TYPE, + + START_NAMESPACE = RES_XML_START_NAMESPACE_TYPE, + END_NAMESPACE = RES_XML_END_NAMESPACE_TYPE, + START_TAG = RES_XML_START_ELEMENT_TYPE, + END_TAG = RES_XML_END_ELEMENT_TYPE, + TEXT = RES_XML_CDATA_TYPE + }; + + const ResXMLTree &mTree; + event_code_t mEventCode; + const ResXMLTree_node *mCurNode; + const void *mCurExt; +}; + +class ResStringPool { + +public: + status_t mError; + void *mOwnedData; + const void *mHeader; + size_t mSize; + mutable pthread_mutex_t mDecodeLock; + const uint32_t *mEntries; + const uint32_t *mEntryStyles; + const void *mStrings; + char16_t mutable **mCache; + uint32_t mStringPoolSize; // number of uint16_t + const uint32_t *mStyles; + uint32_t mStylePoolSize; // number of uint32_t +}; + + +class ResXMLTree : public ResXMLParser { + +public: + void *mDynamicRefTable; + status_t mError; + void *mOwnedData; + const void *mHeader; + size_t mSize; + const uint8_t *mDataEnd; + ResStringPool mStrings; + const uint32_t *mResIds; + size_t mNumResIds; + const ResXMLTree_node *mRootNode; + const void *mRootExt; + event_code_t mRootCode; +}; + +struct ResStringPool_ref { + + // Index into the string pool table (uint32_t-offset from the indices + // immediately after ResStringPool_header) at which to find the location + // of the string data in the pool. + uint32_t index; +}; + +struct ResXMLTree_attrExt { + + // String of the full namespace of this element. + struct ResStringPool_ref ns; + + // String name of this node if it is an ELEMENT; the raw + // character data if this is a CDATA node. + struct ResStringPool_ref name; + + // Byte offset from the start of this structure where the attributes start. + uint16_t attributeStart; + + // Size of the ResXMLTree_attribute structures that follow. + uint16_t attributeSize; + + // Number of attributes associated with an ELEMENT. These are + // available as an array of ResXMLTree_attribute structures + // immediately following this node. + uint16_t attributeCount; + + // Index (1-based) of the "id" attribute. 0 if none. + uint16_t idIndex; + + // Index (1-based) of the "class" attribute. 0 if none. + uint16_t classIndex; + + // Index (1-based) of the "style" attribute. 0 if none. + uint16_t styleIndex; +}; + +struct Res_value { + + // Number of bytes in this structure. + uint16_t size; + // Always set to 0. + uint8_t res0; + + // Type of the data value. + enum : uint8_t { + // The 'data' is either 0 or 1, specifying this resource is either + // undefined or empty, respectively. + TYPE_NULL = 0x00, + // The 'data' holds a ResTable_ref, a reference to another resource + // table entry. + TYPE_REFERENCE = 0x01, + // The 'data' holds an attribute resource identifier. + TYPE_ATTRIBUTE = 0x02, + // The 'data' holds an index into the containing resource table's + // global value string pool. + TYPE_STRING = 0x03, + // The 'data' holds a single-precision floating point number. + TYPE_FLOAT = 0x04, + // The 'data' holds a complex number encoding a dimension value, + // such as "100in". + TYPE_DIMENSION = 0x05, + // The 'data' holds a complex number encoding a fraction of a + // container. + TYPE_FRACTION = 0x06, + // The 'data' holds a dynamic ResTable_ref, which needs to be + // resolved before it can be used like a TYPE_REFERENCE. + TYPE_DYNAMIC_REFERENCE = 0x07, + // The 'data' holds an attribute resource identifier, which needs to be resolved + // before it can be used like a TYPE_ATTRIBUTE. + TYPE_DYNAMIC_ATTRIBUTE = 0x08, + // Beginning of integer flavors... + TYPE_FIRST_INT = 0x10, + // The 'data' is a raw integer value of the form n..n. + TYPE_INT_DEC = 0x10, + // The 'data' is a raw integer value of the form 0xn..n. + TYPE_INT_HEX = 0x11, + // The 'data' is either 0 or 1, for input "false" or "true" respectively. + TYPE_INT_BOOLEAN = 0x12, + // Beginning of color integer flavors... + TYPE_FIRST_COLOR_INT = 0x1c, + // The 'data' is a raw integer value of the form #aarrggbb. + TYPE_INT_COLOR_ARGB8 = 0x1c, + // The 'data' is a raw integer value of the form #rrggbb. + TYPE_INT_COLOR_RGB8 = 0x1d, + // The 'data' is a raw integer value of the form #argb. + TYPE_INT_COLOR_ARGB4 = 0x1e, + // The 'data' is a raw integer value of the form #rgb. + TYPE_INT_COLOR_RGB4 = 0x1f, + // ...end of integer flavors. + TYPE_LAST_COLOR_INT = 0x1f, + // ...end of integer flavors. + TYPE_LAST_INT = 0x1f + }; + uint8_t dataType; + // Structure of complex data values (TYPE_UNIT and TYPE_FRACTION) + enum { + // Where the unit type information is. This gives us 16 possible + // types, as defined below. + COMPLEX_UNIT_SHIFT = 0, + COMPLEX_UNIT_MASK = 0xf, + // TYPE_DIMENSION: Value is raw pixels. + COMPLEX_UNIT_PX = 0, + // TYPE_DIMENSION: Value is Device Independent Pixels. + COMPLEX_UNIT_DIP = 1, + // TYPE_DIMENSION: Value is a Scaled device independent Pixels. + COMPLEX_UNIT_SP = 2, + // TYPE_DIMENSION: Value is in points. + COMPLEX_UNIT_PT = 3, + // TYPE_DIMENSION: Value is in inches. + COMPLEX_UNIT_IN = 4, + // TYPE_DIMENSION: Value is in millimeters. + COMPLEX_UNIT_MM = 5, + // TYPE_FRACTION: A basic fraction of the overall size. + COMPLEX_UNIT_FRACTION = 0, + // TYPE_FRACTION: A fraction of the parent size. + COMPLEX_UNIT_FRACTION_PARENT = 1, + // Where the radix information is, telling where the decimal place + // appears in the mantissa. This give us 4 possible fixed point + // representations as defined below. + COMPLEX_RADIX_SHIFT = 4, + COMPLEX_RADIX_MASK = 0x3, + // The mantissa is an integral number -- i.e., 0xnnnnnn.0 + COMPLEX_RADIX_23p0 = 0, + // The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn + COMPLEX_RADIX_16p7 = 1, + // The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn + COMPLEX_RADIX_8p15 = 2, + // The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn + COMPLEX_RADIX_0p23 = 3, + // Where the actual value is. This gives us 23 bits of + // precision. The top bit is the sign. + COMPLEX_MANTISSA_SHIFT = 8, + COMPLEX_MANTISSA_MASK = 0xffffff + }; + // Possible data values for TYPE_NULL. + enum { + // The value is not defined. + DATA_NULL_UNDEFINED = 0, + // The value is explicitly defined as empty. + DATA_NULL_EMPTY = 1 + }; + // The data for this item, as interpreted according to dataType. + typedef uint32_t data_type; + data_type data; +}; + +struct ResXMLTree_attribute { + // Namespace of this attribute. + struct ResStringPool_ref ns; + + // Name of this attribute. + struct ResStringPool_ref name; + + // The original raw string value of this attribute. + struct ResStringPool_ref rawValue; + + // Processesd typed value of this attribute. + struct Res_value typedValue; +}; + +#endif //EDXPOSED_TEMP_RESOURCE_HOOK_H diff --git a/edxp-core/jni/main/yahfa/HookMain.c b/edxp-core/jni/main/yahfa/HookMain.c index ac317049..a3c522ab 100644 --- a/edxp-core/jni/main/yahfa/HookMain.c +++ b/edxp-core/jni/main/yahfa/HookMain.c @@ -203,7 +203,9 @@ static void ensureMethodCached(void *hookMethod, void *backupMethod, int methodIndex = read32( (void *) ((char *) backupMethod + OFFSET_dex_method_index_in_ArtMethod)); - LOGI("methodIndex = %d", methodIndex); + if (methodIndex >= 512) { + LOGW("methodIndex = %d", methodIndex); + } // update the cached method manually // first we find the array of cached methods diff --git a/edxp-core/template_override/common/util_functions.sh b/edxp-core/template_override/common/util_functions.sh index 180310da..068bd15e 100644 --- a/edxp-core/template_override/common/util_functions.sh +++ b/edxp-core/template_override/common/util_functions.sh @@ -1,6 +1,6 @@ #!/system/bin/sh -EDXP_VERSION="0.3.1.8_beta-SNAPSHOT (3180)" +EDXP_VERSION="0.4.0.1_beta-SNAPSHOT (4010)" ANDROID_SDK=`getprop ro.build.version.sdk` BUILD_DESC=`getprop ro.build.description` PRODUCT=`getprop ro.build.product` diff --git a/edxp-core/template_override/config.sh b/edxp-core/template_override/config.sh index 9d02c8cd..83406ddb 100644 --- a/edxp-core/template_override/config.sh +++ b/edxp-core/template_override/config.sh @@ -41,7 +41,7 @@ LATESTARTSERVICE=false print_modname() { ui_print "************************************" - ui_print " Riru - Ed Xposed v0.3.1.8 " + ui_print " Riru - Ed Xposed v0.4.0.1 " ui_print "************************************" } diff --git a/edxp-core/template_override/module.prop b/edxp-core/template_override/module.prop index fae2ae55..9473fddb 100644 --- a/edxp-core/template_override/module.prop +++ b/edxp-core/template_override/module.prop @@ -1,7 +1,7 @@ id=riru_edxposed name=Riru - Ed Xposed -version=v0.3.1.8_beta-SNAPSHOT -versionCode=3180 +version=v0.4.0.1_beta-SNAPSHOT +versionCode=4010 author=solohsu & MlgmXyysd description=Magisk version of Xposed. Require Riru - Core installed. minMagisk=17000 diff --git a/edxp-core/template_override/riru_module.prop b/edxp-core/template_override/riru_module.prop index d45abdd1..25152672 100644 --- a/edxp-core/template_override/riru_module.prop +++ b/edxp-core/template_override/riru_module.prop @@ -1,5 +1,5 @@ name=Ed Xposed -version=v0.3.1.8_beta-SNAPSHOT -versionCode=3180 +version=v0.4.0.1_beta-SNAPSHOT +versionCode=4010 author=solohsu & MlgmXyysd description=Magisk version of Xposed. Require Riru - Core installed. diff --git a/edxp-sandhook/build.gradle b/edxp-sandhook/build.gradle index 9ee9c429..415ecdfc 100644 --- a/edxp-sandhook/build.gradle +++ b/edxp-sandhook/build.gradle @@ -13,7 +13,7 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } @@ -24,7 +24,7 @@ dependencies { compileOnly files("${hiddenApiStubJarFilePath}") implementation project(':edxp-common') implementation project(':xposed-bridge') - implementation 'com.swift.sandhook:hooklib:3.5.6' + implementation 'com.swift.sandhook:hooklib:3.5.8' compileOnly project(':dexmaker') } @@ -58,7 +58,7 @@ afterEvaluate { task("makeAndCopy${variantNameCapped}", type: Jar, dependsOn: "assemble${variantNameCapped}") { dependsOn tasks.getByPath(":edxp-common:copyCommonProperties") - from "${buildDir}/intermediates/dex/${variantNameLowered}/mergeDex${variantNameCapped}/out/" + from "${buildDir}/intermediates/transforms/dexMerger/${variantNameLowered}/0/" destinationDir file(myTemplatePath + "system/framework/") baseName "edxp" doLast { diff --git a/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/Main.java b/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/Main.java index ba3214c2..ef87ebb1 100644 --- a/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/Main.java +++ b/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/Main.java @@ -1,6 +1,8 @@ package com.elderdrivers.riru.edxp; import android.annotation.SuppressLint; +import android.content.res.Resources; +import android.content.res.XResources; import android.os.Build; import android.os.Process; @@ -147,4 +149,8 @@ public class Main implements KeepAll { public static native void resumeAllThreads(long obj); public static native int waitForGcToComplete(long thread); + + public static native boolean initXResourcesNative(); + + public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes); } diff --git a/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/config/SandHookProvider.java b/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/config/SandHookProvider.java index 7db9e367..b8279053 100644 --- a/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/config/SandHookProvider.java +++ b/edxp-sandhook/src/main/java/com/elderdrivers/riru/edxp/sandhook/config/SandHookProvider.java @@ -1,5 +1,7 @@ package com.elderdrivers.riru.edxp.sandhook.config; +import android.content.res.Resources; +import android.content.res.XResources; import android.util.Log; import com.elderdrivers.riru.edxp.Main; @@ -58,4 +60,14 @@ public class SandHookProvider extends BaseHookProvider { public long getMethodId(Member member) { return 0; } + + @Override + public boolean initXResourcesNative() { + return Main.initXResourcesNative(); + } + + @Override + public void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes) { + Main.rewriteXmlReferencesNative(parserPtr, origRes, repRes); + } } diff --git a/edxp-whale/build.gradle b/edxp-whale/build.gradle index 5f41eadd..3d8d17d1 100644 --- a/edxp-whale/build.gradle +++ b/edxp-whale/build.gradle @@ -13,7 +13,7 @@ android { buildTypes { release { - minifyEnabled false + minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } @@ -56,7 +56,7 @@ afterEvaluate { task("makeAndCopy${variantNameCapped}", type: Jar, dependsOn: "assemble${variantNameCapped}") { dependsOn tasks.getByPath(":edxp-common:copyCommonProperties") - from "${buildDir}/intermediates/dex/${variantNameLowered}/mergeDex${variantNameCapped}/out/" + from "${buildDir}/intermediates/transforms/dexMerger/${variantNameLowered}/0/" destinationDir file(myTemplatePath + "system/framework/") baseName "edxp" doLast { diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/Main.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/Main.java index 2bf72fb4..3f1019be 100644 --- a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/Main.java +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/Main.java @@ -1,6 +1,8 @@ package com.elderdrivers.riru.edxp; import android.annotation.SuppressLint; +import android.content.res.Resources; +import android.content.res.XResources; import android.os.Build; import android.os.Process; @@ -138,4 +140,8 @@ public class Main implements KeepAll { public static native void resumeAllThreads(long obj); public static native int waitForGcToComplete(long thread); + + public static native boolean initXResourcesNative(); + + public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes); } diff --git a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/whale/config/WhaleHookProvider.java b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/whale/config/WhaleHookProvider.java index 7919e38f..79303fe3 100644 --- a/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/whale/config/WhaleHookProvider.java +++ b/edxp-whale/src/main/java/com/elderdrivers/riru/edxp/whale/config/WhaleHookProvider.java @@ -1,5 +1,8 @@ package com.elderdrivers.riru.edxp.whale.config; +import android.content.res.Resources; +import android.content.res.XResources; + import com.elderdrivers.riru.edxp.Main; import com.elderdrivers.riru.edxp.config.BaseHookProvider; import com.lody.whale.WhaleRuntime; @@ -54,4 +57,14 @@ public class WhaleHookProvider extends BaseHookProvider { public long getMethodId(Member member) { return WhaleRuntime.getMethodSlot(member); } + + @Override + public boolean initXResourcesNative() { + return Main.initXResourcesNative(); + } + + @Override + public void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes) { + Main.rewriteXmlReferencesNative(parserPtr, origRes, repRes); + } } diff --git a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/Main.java b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/Main.java index d6bb5997..a781e699 100644 --- a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/Main.java +++ b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/Main.java @@ -1,17 +1,18 @@ package com.elderdrivers.riru.edxp; import android.annotation.SuppressLint; +import android.content.res.Resources; +import android.content.res.XResources; import android.os.Build; import android.os.Process; import com.elderdrivers.riru.common.KeepAll; -import com.elderdrivers.riru.edxp.BuildConfig; import com.elderdrivers.riru.edxp.config.InstallerChooser; +import com.elderdrivers.riru.edxp.util.Utils; import com.elderdrivers.riru.edxp.yahfa.core.HookMethodResolver; import com.elderdrivers.riru.edxp.yahfa.entry.Router; import com.elderdrivers.riru.edxp.yahfa.proxy.BlackWhiteListProxy; import com.elderdrivers.riru.edxp.yahfa.proxy.NormalProxy; -import com.elderdrivers.riru.edxp.util.Utils; import java.lang.reflect.Method; import java.util.Arrays; @@ -139,4 +140,8 @@ public class Main implements KeepAll { public static native void resumeAllThreads(long obj); public static native int waitForGcToComplete(long thread); + + public static native boolean initXResourcesNative(); + + public static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes); } diff --git a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/YahfaHookProvider.java b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/YahfaHookProvider.java index c6d76e3e..4cbd0a37 100644 --- a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/YahfaHookProvider.java +++ b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/config/YahfaHookProvider.java @@ -1,5 +1,8 @@ package com.elderdrivers.riru.edxp.yahfa.config; +import android.content.res.Resources; +import android.content.res.XResources; + import com.elderdrivers.riru.edxp.Main; import com.elderdrivers.riru.edxp.config.BaseHookProvider; import com.elderdrivers.riru.edxp.yahfa.dexmaker.DexMakerUtils; @@ -35,4 +38,14 @@ public class YahfaHookProvider extends BaseHookProvider { public void deoptMethodNative(Object method) { Main.deoptMethodNative(method); } + + @Override + public boolean initXResourcesNative() { + return Main.initXResourcesNative(); + } + + @Override + public void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes) { + Main.rewriteXmlReferencesNative(parserPtr, origRes, repRes); + } } diff --git a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/Router.java b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/Router.java index 980ca8f5..57493cab 100644 --- a/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/Router.java +++ b/edxp-yahfa/src/main/java/com/elderdrivers/riru/edxp/yahfa/entry/Router.java @@ -3,6 +3,7 @@ package com.elderdrivers.riru.edxp.yahfa.entry; import android.app.AndroidAppHelper; import android.text.TextUtils; +import com.elderdrivers.riru.edxp.Main; import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal; import com.elderdrivers.riru.edxp.util.Utils; import com.elderdrivers.riru.edxp.yahfa.config.YahfaEdxpConfig; diff --git a/hiddenapi-stubs/libs/framework-stub.jar b/hiddenapi-stubs/libs/framework-stub.jar index 6ae1ebde..e68daf93 100644 Binary files a/hiddenapi-stubs/libs/framework-stub.jar and b/hiddenapi-stubs/libs/framework-stub.jar differ diff --git a/hiddenapi-stubs/src/main/java/android/content/res/Resources.java b/hiddenapi-stubs/src/main/java/android/content/res/Resources.java index 545d7d36..7894d566 100644 --- a/hiddenapi-stubs/src/main/java/android/content/res/Resources.java +++ b/hiddenapi-stubs/src/main/java/android/content/res/Resources.java @@ -25,6 +25,14 @@ public class Resources { throw new UnsupportedOperationException("STUB"); } + public Resources(ClassLoader classLoader) { + throw new UnsupportedOperationException("STUB"); + } + + public void setImpl(ResourcesImpl impl) { + throw new UnsupportedOperationException("STUB"); + } + public static Resources getSystem() { throw new UnsupportedOperationException("STUB"); } diff --git a/hiddenapi-stubs/src/main/java/android/content/res/ResourcesImpl.java b/hiddenapi-stubs/src/main/java/android/content/res/ResourcesImpl.java new file mode 100644 index 00000000..3f29d11d --- /dev/null +++ b/hiddenapi-stubs/src/main/java/android/content/res/ResourcesImpl.java @@ -0,0 +1,4 @@ +package android.content.res; + +public class ResourcesImpl { +} diff --git a/hiddenapi-stubs/src/main/java/android/content/res/TypedArray.java b/hiddenapi-stubs/src/main/java/android/content/res/TypedArray.java index 53256c1b..f45897c2 100644 --- a/hiddenapi-stubs/src/main/java/android/content/res/TypedArray.java +++ b/hiddenapi-stubs/src/main/java/android/content/res/TypedArray.java @@ -8,6 +8,10 @@ public class TypedArray { throw new UnsupportedOperationException("STUB"); } + protected TypedArray(Resources resources) { + throw new UnsupportedOperationException("STUB"); + } + protected TypedArray(Resources resources, int[] data, int[] indices, int len) { throw new UnsupportedOperationException("STUB"); } diff --git a/xposed-bridge/src/main/java/android/content/res/XResources.java b/xposed-bridge/src/main/java/android/content/res/XResources.java index e8d67258..412a7dc6 100644 --- a/xposed-bridge/src/main/java/android/content/res/XResources.java +++ b/xposed-bridge/src/main/java/android/content/res/XResources.java @@ -18,6 +18,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import com.elderdrivers.riru.edxp.config.EdXpConfigGlobal; + import org.xmlpull.v1.XmlPullParser; import java.io.File; @@ -34,8 +36,6 @@ import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet; import de.robv.android.xposed.callbacks.XC_LayoutInflated; import de.robv.android.xposed.callbacks.XC_LayoutInflated.LayoutInflatedParam; import de.robv.android.xposed.callbacks.XCallback; -import xposed.dummy.XResourcesSuperClass; -import xposed.dummy.XTypedArraySuperClass; import static de.robv.android.xposed.XposedHelpers.decrementMethodDepth; import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; @@ -52,7 +52,7 @@ import static de.robv.android.xposed.XposedHelpers.incrementMethodDepth; * be set using the methods made available via the API methods in this class. */ @SuppressWarnings("JniMissingFunction") -public class XResources extends XResourcesSuperClass { +public class XResources extends Resources { private static final SparseArray> sReplacements = new SparseArray<>(); private static final SparseArray> sResourceNames = new SparseArray<>(); @@ -80,11 +80,19 @@ public class XResources extends XResourcesSuperClass { private String mResDir; private String mPackageName; - /** Dummy, will never be called (objects are transferred to this class only). */ - private XResources() { - throw new UnsupportedOperationException(); + public XResources(AssetManager assets, DisplayMetrics metrics, Configuration config) { + super(assets, metrics, config); } + public XResources(ClassLoader classLoader) { + super(classLoader); + } + + /** Dummy, will never be called (objects are transferred to this class only). */ +// private XResources() { +// throw new UnsupportedOperationException(); +// } + /** @hide */ public void initObject(String resDir) { if (mIsObjectInited) @@ -168,7 +176,7 @@ public class XResources extends XResourcesSuperClass { pkgInfo = PackageParser.parsePackageLite(resDir, 0); } if (pkgInfo != null && pkgInfo.packageName != null) { - Log.w(XposedBridge.TAG, "Package name for " + resDir + " had to be retrieved via parser"); +// Log.w(XposedBridge.TAG, "Package name for " + resDir + " had to be retrieved via parser"); packageName = pkgInfo.packageName; setPackageNameForResDir(packageName, resDir); return packageName; @@ -1118,7 +1126,7 @@ public class XResources extends XResourcesSuperClass { } private static boolean isXmlCached(Resources res, int id) { - int[] mCachedXmlBlockIds = (int[]) getObjectField(res, "mCachedXmlBlockIds"); + int[] mCachedXmlBlockIds = (int[]) getObjectField(getObjectField(res, "mResourcesImpl"), "mCachedXmlBlockCookies"); synchronized (mCachedXmlBlockIds) { for (int cachedId : mCachedXmlBlockIds) { if (cachedId == id) @@ -1128,7 +1136,9 @@ public class XResources extends XResourcesSuperClass { return false; } - private static native void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes); + private static void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes) { + EdXpConfigGlobal.getHookProvider().rewriteXmlReferencesNative(parserPtr, origRes, repRes); + } /** * Used to replace reference IDs in XMLs. @@ -1253,12 +1263,17 @@ public class XResources extends XResourcesSuperClass { * Mainly used when inflating layouts. * @hide */ - public static class XTypedArray extends XTypedArraySuperClass { - /** Dummy, will never be called (objects are transferred to this class only). */ - private XTypedArray() { - super(null, null, null, 0); - throw new UnsupportedOperationException(); - } + public static class XTypedArray extends TypedArray { + + public XTypedArray(Resources resources) { + super(resources); + } + + /** Dummy, will never be called (objects are transferred to this class only). */ +// private XTypedArray() { +// super(null, null, null, 0); +// throw new UnsupportedOperationException(); +// } @Override public boolean getBoolean(int index, boolean defValue) { diff --git a/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/config/EdXpConfigGlobal.java b/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/config/EdXpConfigGlobal.java index 95c404a0..6ae7370a 100644 --- a/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/config/EdXpConfigGlobal.java +++ b/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/config/EdXpConfigGlobal.java @@ -1,5 +1,8 @@ package com.elderdrivers.riru.edxp.config; +import android.content.res.Resources; +import android.content.res.XResources; + import com.elderdrivers.riru.edxp.hook.HookProvider; import java.lang.reflect.InvocationTargetException; @@ -88,5 +91,15 @@ public class EdXpConfigGlobal { public void deoptMethodNative(Object method) { } + + @Override + public boolean initXResourcesNative() { + return false; + } + + @Override + public void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes) { + + } }; } diff --git a/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/hook/HookProvider.java b/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/hook/HookProvider.java index 119594db..a8fccd78 100644 --- a/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/hook/HookProvider.java +++ b/xposed-bridge/src/main/java/com/elderdrivers/riru/edxp/hook/HookProvider.java @@ -1,5 +1,8 @@ package com.elderdrivers.riru.edxp.hook; +import android.content.res.Resources; +import android.content.res.XResources; + import java.lang.reflect.Member; import de.robv.android.xposed.XposedBridge; @@ -21,4 +24,8 @@ public interface HookProvider { Object findMethodNative(Class clazz, String methodName, String methodSig); void deoptMethodNative(Object method); + + boolean initXResourcesNative(); + + void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes); } diff --git a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java index 92e69d02..d1711170 100644 --- a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java +++ b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedBridge.java @@ -378,10 +378,9 @@ public final class XposedBridge { * @hide */ public static void hookInitPackageResources(XC_InitPackageResources callback) { - // TODO not supported yet -// synchronized (sInitPackageResourcesCallbacks) { -// sInitPackageResourcesCallbacks.add(callback); -// } + synchronized (sInitPackageResourcesCallbacks) { + sInitPackageResourcesCallbacks.add(callback); + } } public static void clearInitPackageResources() { diff --git a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedInit.java b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedInit.java index bba953fd..4a2d5b87 100644 --- a/xposed-bridge/src/main/java/de/robv/android/xposed/XposedInit.java +++ b/xposed-bridge/src/main/java/de/robv/android/xposed/XposedInit.java @@ -1,6 +1,16 @@ package de.robv.android.xposed; +import android.app.ActivityThread; +import android.app.AndroidAppHelper; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageParser; +import android.content.res.Resources; +import android.content.res.ResourcesImpl; +import android.content.res.TypedArray; +import android.content.res.XResources; import android.os.Build; +import android.os.IBinder; +import android.os.Process; import android.text.TextUtils; import android.util.Log; @@ -12,20 +22,34 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; +import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import dalvik.system.DexFile; import dalvik.system.PathClassLoader; +import de.robv.android.xposed.callbacks.XC_InitPackageResources; +import de.robv.android.xposed.callbacks.XCallback; import de.robv.android.xposed.services.BaseService; +import static de.robv.android.xposed.XposedBridge.hookAllConstructors; +import static de.robv.android.xposed.XposedBridge.hookAllMethods; +import static de.robv.android.xposed.XposedHelpers.callMethod; import static de.robv.android.xposed.XposedHelpers.closeSilently; +import static de.robv.android.xposed.XposedHelpers.findAndHookMethod; import static de.robv.android.xposed.XposedHelpers.findClass; import static de.robv.android.xposed.XposedHelpers.findFieldIfExists; +import static de.robv.android.xposed.XposedHelpers.getObjectField; +import static de.robv.android.xposed.XposedHelpers.getParameterIndexByType; import static de.robv.android.xposed.XposedHelpers.setStaticBooleanField; import static de.robv.android.xposed.XposedHelpers.setStaticLongField; +import static de.robv.android.xposed.XposedHelpers.setStaticObjectField; public final class XposedInit { private static final String TAG = XposedBridge.TAG; @@ -34,7 +58,7 @@ public final class XposedInit { private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication"; // TODO not supported yet - private static boolean disableResources = true; + private static boolean disableResources = false; private static final String[] XRESOURCES_CONFLICTING_PACKAGES = {"com.sygic.aura"}; private XposedInit() { @@ -58,11 +82,220 @@ public final class XposedInit { } catch (NoSuchFieldError ignored) { } } + findAndHookMethod("android.app.ApplicationPackageManager", null, "getResourcesForApplication", + ApplicationInfo.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + ApplicationInfo app = (ApplicationInfo) param.args[0]; + XResources.setPackageNameForResDir(app.packageName, + app.uid == Process.myUid() ? app.sourceDir : app.publicSourceDir); + } + }); + hookResources(); } /*package*/ - static void hookResources() throws Throwable { - // ed: not for now + public static void hookResources() throws Throwable { + + String BASE_DIR = EdXpConfigGlobal.getConfig().getInstallerBaseDir(); + + if (SELinuxHelper.getAppDataFileService().checkFileExists(BASE_DIR + "conf/disable_resources")) { + Log.w(TAG, "Found " + BASE_DIR + "conf/disable_resources, not hooking resources"); + disableResources = true; + return; + } + + if (!EdXpConfigGlobal.getHookProvider().initXResourcesNative()) { + Log.e(TAG, "Cannot hook resources"); + disableResources = true; + return; + } + + /* + * getTopLevelResources(a) + * -> getTopLevelResources(b) + * -> key = new ResourcesKey() + * -> r = new Resources() + * -> mActiveResources.put(key, r) + * -> return r + */ + + final Class classGTLR; + final Class classResKey; + final ThreadLocal latestResKey = new ThreadLocal<>(); + + if (Build.VERSION.SDK_INT <= 18) { + classGTLR = ActivityThread.class; + classResKey = Class.forName("android.app.ActivityThread$ResourcesKey"); + } else { + classGTLR = Class.forName("android.app.ResourcesManager"); + classResKey = Class.forName("android.content.res.ResourcesKey"); + } + + if (Build.VERSION.SDK_INT >= 24) { + hookAllMethods(classGTLR, "getOrCreateResources", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + // At least on OnePlus 5, the method has an additional parameter compared to AOSP. + final int activityTokenIdx = getParameterIndexByType(param.method, IBinder.class); + final int resKeyIdx = getParameterIndexByType(param.method, classResKey); + + String resDir = (String) getObjectField(param.args[resKeyIdx], "mResDir"); + XResources newRes = cloneToXResources(param, resDir); + if (newRes == null) { + return; + } + + Object activityToken = param.args[activityTokenIdx]; + synchronized (param.thisObject) { + ArrayList> resourceReferences; + if (activityToken != null) { + Object activityResources = callMethod(param.thisObject, "getOrCreateActivityResourcesStructLocked", activityToken); + resourceReferences = (ArrayList>) getObjectField(activityResources, "activityResources"); + } else { + resourceReferences = (ArrayList>) getObjectField(param.thisObject, "mResourceReferences"); + } + resourceReferences.add(new WeakReference(newRes)); + } + } + }); + } else { + hookAllConstructors(classResKey, new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + latestResKey.set(param.thisObject); + } + }); + + hookAllMethods(classGTLR, "getTopLevelResources", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + latestResKey.set(null); + } + + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + Object key = latestResKey.get(); + if (key == null) { + return; + } + latestResKey.set(null); + + String resDir = (String) getObjectField(key, "mResDir"); + XResources newRes = cloneToXResources(param, resDir); + if (newRes == null) { + return; + } + + @SuppressWarnings("unchecked") + Map> mActiveResources = + (Map>) getObjectField(param.thisObject, "mActiveResources"); + Object lockObject = (Build.VERSION.SDK_INT <= 18) + ? getObjectField(param.thisObject, "mPackages") : param.thisObject; + + synchronized (lockObject) { + WeakReference existing = mActiveResources.put(key, new WeakReference(newRes)); + if (existing != null && existing.get() != null && existing.get().getAssets() != newRes.getAssets()) { + existing.get().getAssets().close(); + } + } + } + }); + + if (Build.VERSION.SDK_INT >= 19) { + // This method exists only on CM-based ROMs + hookAllMethods(classGTLR, "getTopLevelThemedResources", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + String resDir = (String) param.args[0]; + cloneToXResources(param, resDir); + } + }); + } + } + + // Invalidate callers of methods overridden by XTypedArray +// if (Build.VERSION.SDK_INT >= 24) { +// Set methods = getOverriddenMethods(XResources.XTypedArray.class); +// XposedBridge.invalidateCallersNative(methods.toArray(new Member[methods.size()])); +// } + + // Replace TypedArrays with XTypedArrays +// hookAllConstructors(TypedArray.class, new XC_MethodHook() { +// @Override +// protected void afterHookedMethod(MethodHookParam param) throws Throwable { +// TypedArray typedArray = (TypedArray) param.thisObject; +// Resources res = typedArray.getResources(); +// if (res instanceof XResources) { +// XResources.XTypedArray newTypedArray = new XResources.XTypedArray(res); +// XposedBridge.setObjectClass(typedArray, XResources.XTypedArray.class); +// } +// } +// }); + + findAndHookMethod(TypedArray.class, "obtain", Resources.class, int.class, + new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + if (param.getResult() instanceof XResources.XTypedArray) { + return; + } + if (!(param.args[0] instanceof XResources)) { + return; + } + XResources.XTypedArray newResult = + new XResources.XTypedArray((Resources) param.args[0]); + int len = (int) param.args[1]; + Method resizeMethod = XposedHelpers.findMethodBestMatch( + TypedArray.class, "resize", new Class[]{int.class}); + resizeMethod.setAccessible(true); + resizeMethod.invoke(newResult, len); + param.setResult(newResult); + } + }); + + // Replace system resources + XResources systemRes = new XResources( + (ClassLoader) XposedHelpers.getObjectField(Resources.getSystem(), "mClassLoader")); + systemRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl")); + systemRes.initObject(null); + setStaticObjectField(Resources.class, "mSystem", systemRes); + + XResources.init(latestResKey); + + //custom + hookAllConstructors(PackageParser.PackageParserException.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + XposedBridge.log(new Throwable()); + } + }); + } + + private static XResources cloneToXResources(XC_MethodHook.MethodHookParam param, String resDir) { + Object result = param.getResult(); + if (result == null || result instanceof XResources || + Arrays.binarySearch(XRESOURCES_CONFLICTING_PACKAGES, AndroidAppHelper.currentPackageName()) == 0) { + return null; + } + + // Replace the returned resources with our subclass. + XResources newRes = new XResources( + (ClassLoader) XposedHelpers.getObjectField(param.getResult(), "mClassLoader")); + newRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl")); + newRes.initObject(resDir); + + // Invoke handleInitPackageResources(). + if (newRes.isFirstLoad()) { + String packageName = newRes.getPackageName(); + XC_InitPackageResources.InitPackageResourcesParam resparam = new XC_InitPackageResources.InitPackageResourcesParam(XposedBridge.sInitPackageResourcesCallbacks); + resparam.packageName = packageName; + resparam.res = newRes; + XCallback.callAll(resparam); + } + + param.setResult(newRes); + return newRes; } private static boolean needsToCloseFilesForFork() {