From 711c589088874804f8d0489f579bc4444a528b06 Mon Sep 17 00:00:00 2001 From: solohsu Date: Sun, 21 Apr 2019 11:46:58 +0800 Subject: [PATCH] Add support for resources hooking. (1/2) --- edxp-core/jni/main/yahfa/HookMain.c | 4 +- hiddenapi-stubs/libs/framework-stub.jar | Bin 14886 -> 15348 bytes .../java/android/content/res/Resources.java | 8 + .../android/content/res/ResourcesImpl.java | 4 + .../java/android/content/res/TypedArray.java | 4 + .../java/android/content/res/XResources.java | 265 +++++++++--------- .../de/robv/android/xposed/XposedBridge.java | 7 +- .../de/robv/android/xposed/XposedInit.java | 233 ++++++++++++++- 8 files changed, 390 insertions(+), 135 deletions(-) create mode 100644 hiddenapi-stubs/src/main/java/android/content/res/ResourcesImpl.java 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/hiddenapi-stubs/libs/framework-stub.jar b/hiddenapi-stubs/libs/framework-stub.jar index 6ae1ebdec61fa3e4eed65942bc6730b3eb17e0df..b1d3a0fb55a4d3f9d54c79159a43bc35caf6e62e 100644 GIT binary patch delta 4013 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 NhzSe=Ns|NF{{>U$ttkKi delta 3584 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 DPmVau 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..c8a4fad9 100644 --- a/xposed-bridge/src/main/java/android/content/res/XResources.java +++ b/xposed-bridge/src/main/java/android/content/res/XResources.java @@ -34,8 +34,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 +50,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 +78,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 +174,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; @@ -624,28 +630,28 @@ public class XResources extends XResourcesSuperClass { } } - /** @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 @@ -937,76 +943,76 @@ public class XResources extends XResourcesSuperClass { 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 @@ -1094,28 +1100,28 @@ public class XResources extends XResourcesSuperClass { 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 (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"); @@ -1253,12 +1259,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/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..ca87553b 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,214 @@ 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; + } + + /* + * 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() {