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..599fd613 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,9 @@ public abstract class BaseHookProvider implements HookProvider { public void deoptMethodNative(Object method) { } + + @Override + public void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes) { + + } } 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..4f485bac --- /dev/null +++ b/edxp-core/jni/main/native_hook/resource_hook.cpp @@ -0,0 +1,161 @@ +// +// 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)(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; + const ResXMLTree& mTree = parser->mTree; + uint32_t* mResIds = (uint32_t*)mTree.mResIds; + ResXMLTree_attrExt* tag; + int attrCount; + + if (parser == NULL) + return; + + 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..52e91bbd --- /dev/null +++ b/edxp-core/jni/main/native_hook/resource_hook.h @@ -0,0 +1,256 @@ +// +// 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); + +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 + }; + +public: + friend class ResXMLTree; + + event_code_t nextNode(); + const ResXMLTree& mTree; + event_code_t mEventCode; + const ResXMLTree_node* mCurNode; + const void* mCurExt; +}; + + +class ResXMLTree : public ResXMLParser +{ +public: + friend class ResXMLParser; + int32_t validateNode(const ResXMLTree_node* node) const; + void* mDynamicRefTable; + int32_t mError; + void* mOwnedData; + const void* mHeader; + size_t mSize; + const uint8_t* mDataEnd; + void* mStrings; + const uint32_t* mResIds; + size_t mNumResIds; + const ResXMLTree_node* mRootNode; + const void* mRootExt; + event_code_t mRootCode; +}; + +struct ResXMLTree_attrExt +{ + // String of the full namespace of this element. + void* ns; + + // String name of this node if it is an ELEMENT; the raw + // character data if this is a CDATA node. + void* 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; + void copyFrom_dtoh(const Res_value& src); +}; + +struct ResXMLTree_attribute +{ + // Namespace of this attribute. + void* ns; + + // Name of this attribute. + void* name; + // The original raw string value of this attribute. + void* rawValue; + + // Processesd typed value of this attribute. + struct Res_value typedValue; +}; + +#endif //EDXPOSED_TEMP_RESOURCE_HOOK_H 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 39e8a516..f000ae59 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,9 @@ 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; import com.elderdrivers.riru.edxp.yahfa.dexmaker.DynamicBridge; @@ -24,4 +28,9 @@ public class YahfaHookProvider extends BaseHookProvider { public Member findMethodNative(Member hookMethod) { return DexMakerUtils.findMethodNative(hookMethod); } + + @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..5eae8a75 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; @@ -57,6 +58,7 @@ public class Router { } Router.startBootstrapHook(isSystem); XposedInit.initForZygote(isSystem); + Main.initXResourcesNative(); } catch (Throwable t) { Utils.logE("error during Xposed initialization", t); XposedBridge.disableHooks = true; 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 c8a4fad9..7d8de8e3 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; @@ -630,28 +632,28 @@ public class XResources extends Resources { } } -// /** @hide */ -// @Override -// public XmlResourceParser getAnimation(int id) throws NotFoundException { -// Object replacement = getReplacement(id); -// if (replacement instanceof XResForwarder) { -// Resources repRes = ((XResForwarder) replacement).getResources(); -// int repId = ((XResForwarder) replacement).getId(); -// -// boolean loadedFromCache = isXmlCached(repRes, repId); -// XmlResourceParser result = repRes.getAnimation(repId); -// -// if (!loadedFromCache) { -// long parseState = (Build.VERSION.SDK_INT >= 21) -// ? getLongField(result, "mParseState") -// : getIntField(result, "mParseState"); -// rewriteXmlReferencesNative(parseState, this, repRes); -// } -// -// return result; -// } -// return super.getAnimation(id); -// } + /** @hide */ + @Override + public XmlResourceParser getAnimation(int id) throws NotFoundException { + Object replacement = getReplacement(id); + if (replacement instanceof XResForwarder) { + Resources repRes = ((XResForwarder) replacement).getResources(); + int repId = ((XResForwarder) replacement).getId(); + + boolean loadedFromCache = isXmlCached(repRes, repId); + XmlResourceParser result = repRes.getAnimation(repId); + + if (!loadedFromCache) { + long parseState = (Build.VERSION.SDK_INT >= 21) + ? getLongField(result, "mParseState") + : getIntField(result, "mParseState"); + rewriteXmlReferencesNative(parseState, this, repRes); + } + + return result; + } + return super.getAnimation(id); + } /** @hide */ @Override @@ -943,76 +945,76 @@ public class XResources extends Resources { return super.getIntArray(id); } -// /** @hide */ -// @Override -// public XmlResourceParser getLayout(int id) throws NotFoundException { -// XmlResourceParser result; -// Object replacement = getReplacement(id); -// if (replacement instanceof XResForwarder) { -// Resources repRes = ((XResForwarder) replacement).getResources(); -// int repId = ((XResForwarder) replacement).getId(); -// -// boolean loadedFromCache = isXmlCached(repRes, repId); -// result = repRes.getLayout(repId); -// -// if (!loadedFromCache) { -// long parseState = (Build.VERSION.SDK_INT >= 21) -// ? getLongField(result, "mParseState") -// : getIntField(result, "mParseState"); -// rewriteXmlReferencesNative(parseState, this, repRes); -// } -// } else { -// result = super.getLayout(id); -// } -// -// // Check whether this layout is hooked -// HashMap> inner; -// synchronized (sLayoutCallbacks) { -// inner = sLayoutCallbacks.get(id); -// } -// if (inner != null) { -// CopyOnWriteSortedSet callbacks; -// synchronized (inner) { -// callbacks = inner.get(mResDir); -// if (callbacks == null && mResDir != null) -// callbacks = inner.get(null); -// } -// if (callbacks != null) { -// String variant = "layout"; -// TypedValue value = (TypedValue) getObjectField(this, "mTmpValue"); -// getValue(id, value, true); -// if (value.type == TypedValue.TYPE_STRING) { -// String[] components = value.string.toString().split("/", 3); -// if (components.length == 3) -// variant = components[1]; -// else -// XposedBridge.log("Unexpected resource path \"" + value.string.toString() -// + "\" for resource id 0x" + Integer.toHexString(id)); -// } else { -// XposedBridge.log(new NotFoundException("Could not find file name for resource id 0x") + Integer.toHexString(id)); -// } -// -// synchronized (sXmlInstanceDetails) { -// synchronized (sResourceNames) { -// HashMap resNamesInner = sResourceNames.get(id); -// if (resNamesInner != null) { -// synchronized (resNamesInner) { -// XMLInstanceDetails details = new XMLInstanceDetails(resNamesInner.get(mResDir), variant, callbacks); -// sXmlInstanceDetails.put(result, details); -// -// // if we were called inside LayoutInflater.parseInclude, store the details for it -// MethodHookParam top = sIncludedLayouts.get().peek(); -// if (top != null) -// top.setObjectExtra(EXTRA_XML_INSTANCE_DETAILS, details); -// } -// } -// } -// } -// } -// } -// -// return result; -// } + /** @hide */ + @Override + public XmlResourceParser getLayout(int id) throws NotFoundException { + XmlResourceParser result; + Object replacement = getReplacement(id); + if (replacement instanceof XResForwarder) { + Resources repRes = ((XResForwarder) replacement).getResources(); + int repId = ((XResForwarder) replacement).getId(); + + boolean loadedFromCache = isXmlCached(repRes, repId); + result = repRes.getLayout(repId); + + if (!loadedFromCache) { + long parseState = (Build.VERSION.SDK_INT >= 21) + ? getLongField(result, "mParseState") + : getIntField(result, "mParseState"); + rewriteXmlReferencesNative(parseState, this, repRes); + } + } else { + result = super.getLayout(id); + } + + // Check whether this layout is hooked + HashMap> inner; + synchronized (sLayoutCallbacks) { + inner = sLayoutCallbacks.get(id); + } + if (inner != null) { + CopyOnWriteSortedSet callbacks; + synchronized (inner) { + callbacks = inner.get(mResDir); + if (callbacks == null && mResDir != null) + callbacks = inner.get(null); + } + if (callbacks != null) { + String variant = "layout"; + TypedValue value = (TypedValue) getObjectField(this, "mTmpValue"); + getValue(id, value, true); + if (value.type == TypedValue.TYPE_STRING) { + String[] components = value.string.toString().split("/", 3); + if (components.length == 3) + variant = components[1]; + else + XposedBridge.log("Unexpected resource path \"" + value.string.toString() + + "\" for resource id 0x" + Integer.toHexString(id)); + } else { + XposedBridge.log(new NotFoundException("Could not find file name for resource id 0x") + Integer.toHexString(id)); + } + + synchronized (sXmlInstanceDetails) { + synchronized (sResourceNames) { + HashMap resNamesInner = sResourceNames.get(id); + if (resNamesInner != null) { + synchronized (resNamesInner) { + XMLInstanceDetails details = new XMLInstanceDetails(resNamesInner.get(mResDir), variant, callbacks); + sXmlInstanceDetails.put(result, details); + + // if we were called inside LayoutInflater.parseInclude, store the details for it + MethodHookParam top = sIncludedLayouts.get().peek(); + if (top != null) + top.setObjectExtra(EXTRA_XML_INSTANCE_DETAILS, details); + } + } + } + } + } + } + + return result; + } /** @hide */ @Override @@ -1100,31 +1102,34 @@ public class XResources extends Resources { return super.getTextArray(id); } -// /** @hide */ -// @Override -// public XmlResourceParser getXml(int id) throws NotFoundException { -// Object replacement = getReplacement(id); -// if (replacement instanceof XResForwarder) { -// Resources repRes = ((XResForwarder) replacement).getResources(); -// int repId = ((XResForwarder) replacement).getId(); -// -// boolean loadedFromCache = isXmlCached(repRes, repId); -// XmlResourceParser result = repRes.getXml(repId); -// -// if (!loadedFromCache) { -// long parseState = (Build.VERSION.SDK_INT >= 21) -// ? getLongField(result, "mParseState") -// : getIntField(result, "mParseState"); -// rewriteXmlReferencesNative(parseState, this, repRes); -// } -// -// return result; -// } -// return super.getXml(id); -// } + /** @hide */ + @Override + public XmlResourceParser getXml(int id) throws NotFoundException { + Object replacement = getReplacement(id); + if (id == 2131034112) { + XposedBridge.log("2131034112: " + replacement); + } + if (replacement instanceof XResForwarder) { + Resources repRes = ((XResForwarder) replacement).getResources(); + int repId = ((XResForwarder) replacement).getId(); + + boolean loadedFromCache = isXmlCached(repRes, repId); + XmlResourceParser result = repRes.getXml(repId); + + if (!loadedFromCache) { + long parseState = (Build.VERSION.SDK_INT >= 21) + ? getLongField(result, "mParseState") + : getIntField(result, "mParseState"); + rewriteXmlReferencesNative(parseState, this, repRes); + } + + return result; + } + return super.getXml(id); + } 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) @@ -1134,7 +1139,9 @@ public class XResources extends Resources { 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. 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..322f414a 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,10 @@ public class EdXpConfigGlobal { public void deoptMethodNative(Object method) { } + + @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..d1759415 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,6 @@ public interface HookProvider { Object findMethodNative(Class clazz, String methodName, String methodSig); void deoptMethodNative(Object method); + + void rewriteXmlReferencesNative(long parserPtr, XResources origRes, Resources repRes); }