Open kmark opened 8 years ago
My current workaround is to just hook decodeResource in the module, check the id, and if it's the one I need to replace feed it with the DrawableBitmap
created in the DrawableLoader
.
Given the 0xdeadbeef
replacement above the following would replace it in decodeResource without duplicating the loader logic:
findAndHookMethod(BitmapFactory.class, "decodeResource", Resources.class, int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
int id = (int)param.args[1];
if(id == 0xdeadbeef) {
param.setResult(((BitmapDrawable) ((Resources) param.args[0]).getDrawable(id)).getBitmap());
}
}
});
Of course if there were an exception or the ID did not match, the hook would fall through and the native decodeResource would be executed. The primary advantage here is that there's no need to get messy with the internals of resources, asset cookies, etc. when the end goal is to just return a Bitmap.
To avoid hardcoding the resource IDs it's possible to run Resources.getIdentifier
"once" and then just cache the values in an object field.
For reference, to make this work with decodeResource(Resources res, int id, BitmapFactory.Options opts)
too, I’m using this based on @kmark’s solution:
XposedHelpers.findAndHookMethod(BitmapFactory.class, "decodeResource", Resources.class, int.class, BitmapFactory.Options.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
int id = (int)param.args[1];
if(id == 0xdeadbeef) {
Resources res = (Resources)param.args[0];
BitmapFactory.Options options = (BitmapFactory.Options)param.args[2];
Bitmap b = ((BitmapDrawable)res.getDrawable(id)).getBitmap();
// We could return b now, except that we’d ignore the options. Therefore, marshal
// the Bitmap to a PNG byte[] and use BitmapFactory.decodeByteArray() with our
// caller’s options on this PNG.
ByteArrayOutputStream s = new ByteArrayOutputStream();
b.compress(Bitmap.CompressFormat.PNG, 100, s);
byte[] png = s.toByteArray();
Bitmap ret = BitmapFactory.decodeByteArray(png, 0, png.length, options);
param.setResult(ret);
}
}
});
This could be made more efficient by storing the byte[] in a static attribute, but decodeResource() in my case is only called once, so I didn’t really bother. :man_shrugging:
Given the following example resource hook:
We might expect
0xdeadbeef
to now universally refer to a red BitmapDrawable and every instance of the original drawable to be replaced. Akin to physically replacing the image in its drawable-dpi directory. Unfortunately this is not true whenever the resource is accessed via BitmapFactory.decodeResource:b
contains the original resource not our red square. The following yields the expected replaced Bitmap:The underlying issue is that
Resources.getDrawable
is extended byXResources
to account for replacements whereResources.openRawResource
(called byBitmapFactory.decodeResource
) is not (#53).While I'm hesitant to cause this a bug outright, it does make resource replacement inconsistent for a supported resource type.