Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 36 additions & 27 deletions legacy/src/main/java/android/content/res/XResources.java
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ public class XResources extends XResourcesSuperClass {

private static final SparseArray<HashMap<String, CopyOnWriteSortedSet<XC_LayoutInflated>>> sLayoutCallbacks = new SparseArray<>();
private static final WeakHashMap<XmlResourceParser, XMLInstanceDetails> sXmlInstanceDetails = new WeakHashMap<>();
// The XML blocks whose native tree has already had its module IDs rewritten. See [rewriteXmlReferences].
private static final WeakHashMap<Object, Boolean> sRewrittenXmlBlocks = new WeakHashMap<>();

private static final String EXTRA_XML_INSTANCE_DETAILS = "xmlInstanceDetails";
// No lambda, and no anonymous ThreadLocal either. See the note above [includedLayouts].
Expand Down Expand Up @@ -669,14 +671,8 @@ public XmlResourceParser getAnimation(int id) throws NotFoundException {
Resources repRes = ((XResForwarder) replacement).getResources();
int repId = ((XResForwarder) replacement).getId();

boolean loadedFromCache = isXmlCached(repRes, repId);
XmlResourceParser result = repRes.getAnimation(repId);

if (!loadedFromCache) {
long parseState = getLongField(result, "mParseState");
rewriteXmlReferencesNative(parseState, this, repRes);
}

rewriteXmlReferences(result, repRes);
return result;
}
return super.getAnimation(id);
Expand Down Expand Up @@ -958,13 +954,8 @@ public XmlResourceParser getLayout(int id) throws NotFoundException {
Resources repRes = ((XResForwarder) replacement).getResources();
int repId = ((XResForwarder) replacement).getId();

boolean loadedFromCache = isXmlCached(repRes, repId);
result = repRes.getLayout(repId);

if (!loadedFromCache) {
long parseState = getLongField(result, "mParseState");
rewriteXmlReferencesNative(parseState, this, repRes);
}
rewriteXmlReferences(result, repRes);
} else {
result = super.getLayout(id);
}
Expand Down Expand Up @@ -1144,28 +1135,46 @@ public XmlResourceParser getXml(int id) throws NotFoundException {
Resources repRes = ((XResForwarder) replacement).getResources();
int repId = ((XResForwarder) replacement).getId();

boolean loadedFromCache = isXmlCached(repRes, repId);
XmlResourceParser result = repRes.getXml(repId);

if (!loadedFromCache) {
long parseState = getLongField(result, "mParseState");
rewriteXmlReferencesNative(parseState, this, repRes);
}

rewriteXmlReferences(result, repRes);
return result;
}
return super.getXml(id);
}

private static boolean isXmlCached(Resources res, int id) {
int[] mCachedXmlBlockIds = (int[]) getObjectField(getObjectField(res, "mResourcesImpl"), "mCachedXmlBlockCookies");
synchronized (mCachedXmlBlockIds) {
for (int cachedId : mCachedXmlBlockIds) {
if (cachedId == id)
return true;
/**
* Rewrites the module's IDs in a replacement document, unless that has already happened.
*
* The rewrite mutates the native tree in place, and that tree belongs to the {@code XmlBlock},
* not to the parser: {@code ResourcesImpl} keeps a small cache of blocks and hands out a fresh
* parser over the same block for every later request, so "already rewritten" is a property of
* the block and nothing else. That is why the answer is kept per block here.
*
* Getting it wrong is not merely wasted work. A second pass sees the host IDs the first one
* wrote and feeds them back through {@link #translateResId}, which resolves them against the
* module's table — where the same 0x7f package ID makes an unrelated entry a plausible match —
* and then installs a replacement for whatever it found.
*/
private void rewriteXmlReferences(XmlResourceParser parser, Resources repRes) {
Object block;
try {
block = getObjectField(parser, "mBlock");
} catch (Throwable ignored) {
// A ROM that renamed the field costs the deduplication, not the rewrite.
block = null;
}

if (block != null) {
synchronized (sRewrittenXmlBlocks) {
// Marked before the rewrite rather than after: one that throws part of the way
// through leaves a partly translated tree, and a retry would translate that part
// a second time.
if (sRewrittenXmlBlocks.put(block, Boolean.TRUE) != null)
return;
}
}
return false;

rewriteXmlReferencesNative(getLongField(parser, "mParseState"), this, repRes);
}

/**
Expand Down
5 changes: 2 additions & 3 deletions legacy/src/main/java/de/robv/android/xposed/XposedBridge.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

import org.matrix.vector.util.Utils;
import org.matrix.vector.impl.hooks.VectorNativeHooker;
import org.matrix.vector.impl.hooks.VectorInvocation;
import org.matrix.vector.impl.hooks.VectorLegacyCallback;
import org.matrix.vector.nativebridge.HookBridge;
import org.matrix.vector.nativebridge.ResourcesHook;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
Expand Down Expand Up @@ -298,7 +298,6 @@ public static void hookInitPackageResources(XC_InitPackageResources callback) {
* @param args Arguments for the method call as Object[] array.
* @return The result returned from the invoked method.
* @throws NullPointerException if {@code receiver == null} for a non-static method
* @throws IllegalAccessException if this method is not accessible (see {@link AccessibleObject})
* @throws IllegalArgumentException if the number of arguments doesn't match the number of parameters, the receiver
* is incompatible with the declaring class, or an argument could not be unboxed
* or converted by a widening conversion to the corresponding parameter type
Expand All @@ -314,7 +313,7 @@ public static Object invokeOriginalMethod(Member method, Object thisObject, Obje
throw new IllegalArgumentException("method must be of type Method or Constructor");
}

return HookBridge.invokeOriginalMethod((Executable) method, thisObject, args);
return VectorInvocation.invokeOriginal((Executable) method, thisObject, args);
}

/**
Expand Down
57 changes: 19 additions & 38 deletions native/include/framework/android_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,10 @@ struct ResXMLTree_node {
void *comment;
};

class ResXMLTree;

// Only the four words below are mirrored, and only because mCurExt is where the attributes of the
// current tag live. Everything further in - the tree the parser walks, its string pool, its
// attribute name map - is reached through the exported accessors instead: those members have moved
// in almost every release since Pie, and a struct that guesses at them fails silently.
class ResXMLParser {
public:
enum event_code_t {
Expand All @@ -100,37 +102,32 @@ class ResXMLParser {
TEXT = RES_XML_CDATA_TYPE
};

const ResXMLTree &mTree;
const void *mTree;
event_code_t mEventCode;
const ResXMLTree_node *mCurNode;
const void *mCurExt;
};

// Handled only through pointers the framework hands out, so none of its fields are mirrored: the
// class gained a vtable in Android 11 and a lookup cache in Android 15, and each of those moved
// everything behind it.
class ResStringPool {
public:
status_t mError;
void *mOwnedData;
const void *mHeader;
size_t mSize;
mutable pthread_mutex_t mDecodeLock;
const uint32_t *mEntries;
const uint32_t *mEntryStyles;
const void *mStrings;
char16_t mutable **mCache;
uint32_t mStringPoolSize; // number of uint16_t
const uint32_t *mStyles;
uint32_t mStylePoolSize; // number of uint32_t

using stringAtRet = expected<StringPiece16, NullOrIOError>;

inline static auto stringAtS_ = ("_ZNK7android13ResStringPool8stringAtEjPj"_sym |
"_ZNK7android13ResStringPool8stringAtEmPm"_sym)
.as<stringAtRet (ResStringPool::*)(size_t)>;

inline static auto stringAt_ = ("_ZNK7android13ResStringPool8stringAtEj"_sym |
"_ZNK7android13ResStringPool8stringAtEm"_sym)
// The two overloads are told apart by the arity in their mangled names, and binding one to the
// other's signature links cleanly and then breaks the ABI. EjPj/EmPm is stringAt(idx, outLen)
// returning a raw pointer, which is what Android 11 and older export; Ej/Em is stringAt(idx)
// returning the expected, which replaced it in Android 12 and whose result comes back through
// the indirect-result register a raw-pointer caller never sets.
inline static auto stringAt_ = ("_ZNK7android13ResStringPool8stringAtEjPj"_sym |
"_ZNK7android13ResStringPool8stringAtEmPm"_sym)
.as<const char16_t *(ResStringPool::*)(size_t, size_t *)>;

inline static auto stringAtS_ = ("_ZNK7android13ResStringPool8stringAtEj"_sym |
"_ZNK7android13ResStringPool8stringAtEm"_sym)
.as<stringAtRet (ResStringPool::*)(size_t)>;

StringPiece16 stringAt(size_t idx) const {
if (stringAt_) {
size_t len;
Expand All @@ -150,22 +147,6 @@ class ResStringPool {
}
};

class ResXMLTree : public ResXMLParser {
public:
void *mDynamicRefTable;
status_t mError;
void *mOwnedData;
const void *mHeader;
size_t mSize;
const uint8_t *mDataEnd;
ResStringPool mStrings;
const uint32_t *mResIds;
size_t mNumResIds;
const ResXMLTree_node *mRootNode;
const void *mRootExt;
event_code_t mRootCode;
};

struct ResStringPool_ref {
// Index into the string pool table at which
// to find the location of the string data in the pool.
Expand Down
Loading
Loading