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 6ae1ebde..b1d3a0fb 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..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() {