Add support for resources hooking. (1/2)
This commit is contained in:
parent
1006a3a034
commit
711c589088
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
package android.content.res;
|
||||
|
||||
public class ResourcesImpl {
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HashMap<String, Object>> sReplacements = new SparseArray<>();
|
||||
private static final SparseArray<HashMap<String, ResourceNames>> 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<String, CopyOnWriteSortedSet<XC_LayoutInflated>> inner;
|
||||
synchronized (sLayoutCallbacks) {
|
||||
inner = sLayoutCallbacks.get(id);
|
||||
}
|
||||
if (inner != null) {
|
||||
CopyOnWriteSortedSet<XC_LayoutInflated> 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<String, ResourceNames> 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<String, CopyOnWriteSortedSet<XC_LayoutInflated>> inner;
|
||||
// synchronized (sLayoutCallbacks) {
|
||||
// inner = sLayoutCallbacks.get(id);
|
||||
// }
|
||||
// if (inner != null) {
|
||||
// CopyOnWriteSortedSet<XC_LayoutInflated> 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<String, ResourceNames> 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,13 +1259,18 @@ 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) {
|
||||
Object replacement = ((XResources) getResources()).getReplacement(getResourceId(index, 0));
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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<Object> 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<WeakReference<Resources>> resourceReferences;
|
||||
if (activityToken != null) {
|
||||
Object activityResources = callMethod(param.thisObject, "getOrCreateActivityResourcesStructLocked", activityToken);
|
||||
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(activityResources, "activityResources");
|
||||
} else {
|
||||
resourceReferences = (ArrayList<WeakReference<Resources>>) 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<Object, WeakReference<Resources>> mActiveResources =
|
||||
(Map<Object, WeakReference<Resources>>) getObjectField(param.thisObject, "mActiveResources");
|
||||
Object lockObject = (Build.VERSION.SDK_INT <= 18)
|
||||
? getObjectField(param.thisObject, "mPackages") : param.thisObject;
|
||||
|
||||
synchronized (lockObject) {
|
||||
WeakReference<Resources> existing = mActiveResources.put(key, new WeakReference<Resources>(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<Method> 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() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue