[core] Cleanup Xposed Bridge implementation
This commit is contained in:
parent
535dd32e52
commit
aaa8f4bc40
|
|
@ -56,69 +56,16 @@ public final class AndroidAppHelper {
|
|||
private static final boolean HAS_THEME_CONFIG_PARAMETER;
|
||||
|
||||
static {
|
||||
CLASS_RESOURCES_KEY = (Build.VERSION.SDK_INT < 19) ?
|
||||
findClass("android.app.ActivityThread$ResourcesKey", null)
|
||||
: findClass("android.content.res.ResourcesKey", null);
|
||||
CLASS_RESOURCES_KEY = findClass("android.content.res.ResourcesKey", null);
|
||||
|
||||
HAS_IS_THEMEABLE = findFieldIfExists(CLASS_RESOURCES_KEY, "mIsThemeable") != null;
|
||||
HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && Build.VERSION.SDK_INT >= 21
|
||||
&& findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
|
||||
HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private static Map<Object, WeakReference> getResourcesMap(ActivityThread activityThread) {
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
|
||||
return (Map) getObjectField(resourcesManager, "mResourceImpls");
|
||||
} else if (Build.VERSION.SDK_INT >= 19) {
|
||||
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
|
||||
return (Map) getObjectField(resourcesManager, "mActiveResources");
|
||||
} else {
|
||||
return (Map) getObjectField(activityThread, "mActiveResources");
|
||||
}
|
||||
}
|
||||
|
||||
/* For SDK 15 & 16 */
|
||||
private static Object createResourcesKey(String resDir, float scale) {
|
||||
try {
|
||||
if (HAS_IS_THEMEABLE)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, scale, false);
|
||||
else
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, scale);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* For SDK 17 & 18 & 23 */
|
||||
private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) {
|
||||
try {
|
||||
if (HAS_THEME_CONFIG_PARAMETER)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null);
|
||||
else if (HAS_IS_THEMEABLE)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false);
|
||||
else
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* For SDK 19 - 22 */
|
||||
private static Object createResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale, IBinder token) {
|
||||
try {
|
||||
if (HAS_THEME_CONFIG_PARAMETER)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, null, token);
|
||||
else if (HAS_IS_THEMEABLE)
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, false, token);
|
||||
else
|
||||
return newInstance(CLASS_RESOURCES_KEY, resDir, displayId, overrideConfiguration, scale, token);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
return null;
|
||||
}
|
||||
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
|
||||
return (Map) getObjectField(resourcesManager, "mResourceImpls");
|
||||
}
|
||||
|
||||
/* For SDK 24+ */
|
||||
|
|
@ -144,27 +91,13 @@ public final class AndroidAppHelper {
|
|||
}
|
||||
|
||||
Object resourcesKey;
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class);
|
||||
setFloatField(compatInfo, "applicationScale", resources.hashCode());
|
||||
resourcesKey = createResourcesKey(resDir, null, null, null, Display.DEFAULT_DISPLAY, null, compatInfo);
|
||||
} else if (Build.VERSION.SDK_INT == 23) {
|
||||
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
|
||||
} else if (Build.VERSION.SDK_INT >= 19) {
|
||||
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode(), null);
|
||||
} else if (Build.VERSION.SDK_INT >= 17) {
|
||||
resourcesKey = createResourcesKey(resDir, Display.DEFAULT_DISPLAY, null, resources.hashCode());
|
||||
} else {
|
||||
resourcesKey = createResourcesKey(resDir, resources.hashCode());
|
||||
}
|
||||
CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class);
|
||||
setFloatField(compatInfo, "applicationScale", resources.hashCode());
|
||||
resourcesKey = createResourcesKey(resDir, null, null, null, Display.DEFAULT_DISPLAY, null, compatInfo);
|
||||
|
||||
if (resourcesKey != null) {
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
Object resImpl = getObjectField(resources, "mResourcesImpl");
|
||||
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl));
|
||||
} else {
|
||||
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resources));
|
||||
}
|
||||
Object resImpl = getObjectField(resources, "mResourcesImpl");
|
||||
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,11 +119,7 @@ public class XResources extends XResourcesSuperClass {
|
|||
|
||||
if (resDir != null) {
|
||||
synchronized (sReplacementsCacheMap) {
|
||||
mReplacementsCache = sReplacementsCacheMap.get(resDir);
|
||||
if (mReplacementsCache == null) {
|
||||
mReplacementsCache = new byte[128];
|
||||
sReplacementsCacheMap.put(resDir, mReplacementsCache);
|
||||
}
|
||||
mReplacementsCache = sReplacementsCacheMap.computeIfAbsent(resDir, k -> new byte[128]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,14 +180,10 @@ public class XResources extends XResourcesSuperClass {
|
|||
return packageName;
|
||||
|
||||
PackageParser.PackageLite pkgInfo;
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
try {
|
||||
pkgInfo = PackageParser.parsePackageLite(new File(resDir), 0);
|
||||
} catch (PackageParserException e) {
|
||||
throw new IllegalStateException("Could not determine package name for " + resDir, e);
|
||||
}
|
||||
} else {
|
||||
pkgInfo = PackageParser.parsePackageLite(resDir, 0);
|
||||
try {
|
||||
pkgInfo = PackageParser.parsePackageLite(new File(resDir), 0);
|
||||
} catch (PackageParserException e) {
|
||||
throw new IllegalStateException("Could not determine package name for " + resDir, e);
|
||||
}
|
||||
if (pkgInfo != null && pkgInfo.packageName != null) {
|
||||
// Log.w(XposedBridge.TAG, "Package name for " + resDir + " had to be retrieved via parser");
|
||||
|
|
@ -265,7 +257,7 @@ public class XResources extends XResourcesSuperClass {
|
|||
XMLInstanceDetails details = (XMLInstanceDetails) param.getObjectExtra(EXTRA_XML_INSTANCE_DETAILS);
|
||||
if (details != null) {
|
||||
LayoutInflatedParam liparam = new LayoutInflatedParam(details.callbacks);
|
||||
ViewGroup group = (ViewGroup) param.args[(Build.VERSION.SDK_INT < 23) ? 1 : 2];
|
||||
ViewGroup group = (ViewGroup) param.args[2];
|
||||
liparam.view = group.getChildAt(group.getChildCount() - 1);
|
||||
liparam.resNames = details.resNames;
|
||||
liparam.variant = details.variant;
|
||||
|
|
@ -274,16 +266,8 @@ public class XResources extends XResourcesSuperClass {
|
|||
}
|
||||
}
|
||||
};
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, View.class,
|
||||
AttributeSet.class, parseIncludeHook);
|
||||
} else if (Build.VERSION.SDK_INT < 23) {
|
||||
findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, View.class,
|
||||
AttributeSet.class, boolean.class, parseIncludeHook);
|
||||
} else {
|
||||
findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, Context.class,
|
||||
View.class, AttributeSet.class, parseIncludeHook);
|
||||
}
|
||||
findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, Context.class,
|
||||
View.class, AttributeSet.class, parseIncludeHook);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -662,9 +646,7 @@ public class XResources extends XResourcesSuperClass {
|
|||
XmlResourceParser result = repRes.getAnimation(repId);
|
||||
|
||||
if (!loadedFromCache) {
|
||||
long parseState = (Build.VERSION.SDK_INT >= 21)
|
||||
? getLongField(result, "mParseState")
|
||||
: getIntField(result, "mParseState");
|
||||
long parseState = getLongField(result, "mParseState");
|
||||
rewriteXmlReferencesNative(parseState, this, repRes);
|
||||
}
|
||||
|
||||
|
|
@ -924,9 +906,7 @@ public class XResources extends XResourcesSuperClass {
|
|||
result = repRes.getLayout(repId);
|
||||
|
||||
if (!loadedFromCache) {
|
||||
long parseState = (Build.VERSION.SDK_INT >= 21)
|
||||
? getLongField(result, "mParseState")
|
||||
: getIntField(result, "mParseState");
|
||||
long parseState = getLongField(result, "mParseState");
|
||||
rewriteXmlReferencesNative(parseState, this, repRes);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1080,9 +1060,7 @@ public class XResources extends XResourcesSuperClass {
|
|||
XmlResourceParser result = repRes.getXml(repId);
|
||||
|
||||
if (!loadedFromCache) {
|
||||
long parseState = (Build.VERSION.SDK_INT >= 21)
|
||||
? getLongField(result, "mParseState")
|
||||
: getIntField(result, "mParseState");
|
||||
long parseState = getLongField(result, "mParseState");
|
||||
rewriteXmlReferencesNative(parseState, this, repRes);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,255 +0,0 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2020 EdXposed Contributors
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package de.robv.android.xposed;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.DigestException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.zip.Adler32;
|
||||
|
||||
import static de.robv.android.xposed.XposedHelpers.inputStreamToByteArray;
|
||||
|
||||
/**
|
||||
* Helper class which can create a very simple .dex file, containing only a class definition
|
||||
* with a super class (no methods, fields, ...).
|
||||
*/
|
||||
/*package*/ class DexCreator {
|
||||
public static File DALVIK_CACHE = new File(Environment.getDataDirectory(), "dalvik-cache");
|
||||
|
||||
/** Returns the default dex file name for the class. */
|
||||
public static File getDefaultFile(String childClz) {
|
||||
return new File(DALVIK_CACHE, "xposed_" + childClz.substring(childClz.lastIndexOf('.') + 1) + ".dex");
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates (or returns) the path to a dex file which defines the superclass of {@clz} as extending
|
||||
* {@code realSuperClz}, which by itself must extend {@code topClz}.
|
||||
*/
|
||||
public static File ensure(String clz, Class<?> realSuperClz, Class<?> topClz) throws IOException {
|
||||
if (!topClz.isAssignableFrom(realSuperClz)) {
|
||||
throw new ClassCastException("Cannot initialize " + clz + " because " + realSuperClz + " does not extend " + topClz);
|
||||
}
|
||||
|
||||
try {
|
||||
return ensure("xposed.dummy." + clz + "SuperClass", realSuperClz);
|
||||
} catch (IOException e) {
|
||||
throw new IOException("Failed to create a superclass for " + clz, e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Like {@link #ensure(File, String, String)}, just for the default dex file name. */
|
||||
public static File ensure(String childClz, Class<?> superClz) throws IOException {
|
||||
return ensure(getDefaultFile(childClz), childClz, superClz.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that the given file is a simple dex file containing the given classes.
|
||||
* Creates the file if that's not the case.
|
||||
*/
|
||||
public static File ensure(File file, String childClz, String superClz) throws IOException {
|
||||
// First check if a valid file exists.
|
||||
try {
|
||||
byte[] dex = inputStreamToByteArray(new FileInputStream(file));
|
||||
if (matches(dex, childClz, superClz)) {
|
||||
return file;
|
||||
} else {
|
||||
file.delete();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
// If not, create a new dex file.
|
||||
byte[] dex = create(childClz, superClz);
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write(dex);
|
||||
fos.close();
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the Dex file fits to the class names.
|
||||
* Assumes that the file has been created with this class.
|
||||
*/
|
||||
public static boolean matches(byte[] dex, String childClz, String superClz) throws IOException {
|
||||
boolean childFirst = childClz.compareTo(superClz) < 0;
|
||||
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
|
||||
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
|
||||
|
||||
int pos = 0xa0;
|
||||
if (pos + childBytes.length + superBytes.length >= dex.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (byte b : childFirst ? childBytes : superBytes) {
|
||||
if (dex[pos++] != b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (byte b : childFirst ? superBytes: childBytes) {
|
||||
if (dex[pos++] != b) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Creates the byte array for the dex file. */
|
||||
public static byte[] create(String childClz, String superClz) throws IOException {
|
||||
boolean childFirst = childClz.compareTo(superClz) < 0;
|
||||
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
|
||||
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
|
||||
int stringsSize = childBytes.length + superBytes.length;
|
||||
int padding = -stringsSize & 3;
|
||||
stringsSize += padding;
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
|
||||
// header
|
||||
out.write("dex\n035\0".getBytes()); // magic
|
||||
out.write(new byte[24]); // placeholder for checksum and signature
|
||||
writeInt(out, 0xfc + stringsSize); // file size
|
||||
writeInt(out, 0x70); // header size
|
||||
writeInt(out, 0x12345678); // endian constant
|
||||
writeInt(out, 0); // link size
|
||||
writeInt(out, 0); // link offset
|
||||
writeInt(out, 0xa4 + stringsSize); // map offset
|
||||
writeInt(out, 2); // strings count
|
||||
writeInt(out, 0x70); // strings offset
|
||||
writeInt(out, 2); // types count
|
||||
writeInt(out, 0x78); // types offset
|
||||
writeInt(out, 0); // prototypes count
|
||||
writeInt(out, 0); // prototypes offset
|
||||
writeInt(out, 0); // fields count
|
||||
writeInt(out, 0); // fields offset
|
||||
writeInt(out, 0); // methods count
|
||||
writeInt(out, 0); // methods offset
|
||||
writeInt(out, 1); // classes count
|
||||
writeInt(out, 0x80); // classes offset
|
||||
writeInt(out, 0x5c + stringsSize); // data size
|
||||
writeInt(out, 0xa0); // data offset
|
||||
|
||||
// string map
|
||||
writeInt(out, 0xa0);
|
||||
writeInt(out, 0xa0 + (childFirst ? childBytes.length : superBytes.length));
|
||||
|
||||
// types
|
||||
writeInt(out, 0); // first type = first string
|
||||
writeInt(out, 1); // second type = second string
|
||||
|
||||
// class definitions
|
||||
writeInt(out, childFirst ? 0 : 1); // class to define = child type
|
||||
writeInt(out, 1); // access flags = public
|
||||
writeInt(out, childFirst ? 1 : 0); // super class = super type
|
||||
writeInt(out, 0); // no interface
|
||||
writeInt(out, -1); // no source file
|
||||
writeInt(out, 0); // no annotations
|
||||
writeInt(out, 0); // no class data
|
||||
writeInt(out, 0); // no static values
|
||||
|
||||
// string data
|
||||
out.write(childFirst ? childBytes : superBytes);
|
||||
out.write(childFirst ? superBytes : childBytes);
|
||||
out.write(new byte[padding]);
|
||||
|
||||
// annotations
|
||||
writeInt(out, 0); // no items
|
||||
|
||||
// map
|
||||
writeInt(out, 7); // items count
|
||||
writeMapItem(out, 0, 1, 0); // header
|
||||
writeMapItem(out, 1, 2, 0x70); // strings
|
||||
writeMapItem(out, 2, 2, 0x78); // types
|
||||
writeMapItem(out, 6, 1, 0x80); // classes
|
||||
writeMapItem(out, 0x2002, 2, 0xa0); // string data
|
||||
writeMapItem(out, 0x1003, 1, 0xa0 + stringsSize); // annotations
|
||||
writeMapItem(out, 0x1000, 1, 0xa4 + stringsSize); // map list
|
||||
|
||||
byte[] buf = out.toByteArray();
|
||||
updateSignature(buf);
|
||||
updateChecksum(buf);
|
||||
return buf;
|
||||
}
|
||||
|
||||
private static void updateSignature(byte[] dex) {
|
||||
// Update SHA-1 signature
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-1");
|
||||
md.update(dex, 32, dex.length - 32);
|
||||
md.digest(dex, 12, 20);
|
||||
} catch (NoSuchAlgorithmException | DigestException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateChecksum(byte[] dex) {
|
||||
// Update Adler32 checksum
|
||||
Adler32 a32 = new Adler32();
|
||||
a32.update(dex, 12, dex.length - 12);
|
||||
int chksum = (int) a32.getValue();
|
||||
dex[8] = (byte) (chksum & 0xff);
|
||||
dex[9] = (byte) (chksum >> 8 & 0xff);
|
||||
dex[10] = (byte) (chksum >> 16 & 0xff);
|
||||
dex[11] = (byte) (chksum >> 24 & 0xff);
|
||||
}
|
||||
|
||||
private static void writeUleb128(OutputStream out, int value) throws IOException {
|
||||
while (value > 0x7f) {
|
||||
out.write((value & 0x7f) | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
out.write(value);
|
||||
}
|
||||
|
||||
private static void writeInt(OutputStream out, int value) throws IOException {
|
||||
out.write(value);
|
||||
out.write(value >> 8);
|
||||
out.write(value >> 16);
|
||||
out.write(value >> 24);
|
||||
}
|
||||
|
||||
private static void writeMapItem(OutputStream out, int type, int count, int offset) throws IOException {
|
||||
writeInt(out, type);
|
||||
writeInt(out, count);
|
||||
writeInt(out, offset);
|
||||
}
|
||||
|
||||
private static byte[] stringToBytes(String s) throws IOException {
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
writeUleb128(bytes, s.length());
|
||||
// This isn't MUTF-8, but should be OK.
|
||||
bytes.write(s.getBytes("UTF-8"));
|
||||
bytes.write(0);
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
|
||||
private DexCreator() {}
|
||||
}
|
||||
|
|
@ -24,9 +24,9 @@ import java.lang.reflect.Member;
|
|||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static de.robv.android.xposed.XposedBridge.hookMethodNative;
|
||||
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
||||
|
||||
import static io.github.lsposed.lspd.nativebridge.PendingHooks.recordPendingMethodNative;
|
||||
|
||||
public final class PendingHooks {
|
||||
|
|
@ -38,7 +38,7 @@ public final class PendingHooks {
|
|||
public synchronized static void hookPendingMethod(Class<?> clazz) {
|
||||
if (sPendingHooks.containsKey(clazz)) {
|
||||
for (Map.Entry<Member, XposedBridge.AdditionalHookInfo> hook : sPendingHooks.get(clazz).entrySet()) {
|
||||
hookMethodNative(hook.getKey(), clazz, 0, hook.getValue());
|
||||
LSPdConfigGlobal.getHookProvider().hookMethod(hook.getKey(), hook.getValue());
|
||||
}
|
||||
sPendingHooks.remove(clazz);
|
||||
}
|
||||
|
|
@ -47,13 +47,7 @@ public final class PendingHooks {
|
|||
public synchronized static void recordPendingMethod(Method hookMethod,
|
||||
XposedBridge.AdditionalHookInfo additionalInfo) {
|
||||
ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> pending =
|
||||
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(),
|
||||
new Function<Class<?>, ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo>>() {
|
||||
@Override
|
||||
public ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> apply(Class<?> aClass) {
|
||||
return new ConcurrentHashMap<>();
|
||||
}
|
||||
});
|
||||
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(), aClass -> new ConcurrentHashMap<>());
|
||||
|
||||
pending.put(hookMethod, additionalInfo);
|
||||
recordPendingMethodNative(hookMethod, hookMethod.getDeclaringClass());
|
||||
|
|
|
|||
|
|
@ -20,12 +20,6 @@
|
|||
|
||||
package de.robv.android.xposed;
|
||||
|
||||
import android.os.SELinux;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import de.robv.android.xposed.services.BaseService;
|
||||
import de.robv.android.xposed.services.DirectAccessService;
|
||||
|
||||
|
|
@ -41,7 +35,8 @@ public final class SELinuxHelper {
|
|||
* @return A boolean indicating whether SELinux is enabled.
|
||||
*/
|
||||
public static boolean isSELinuxEnabled() {
|
||||
return sIsSELinuxEnabled;
|
||||
// lsp: always enabled
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -50,36 +45,8 @@ public final class SELinuxHelper {
|
|||
* @return A boolean indicating whether SELinux is enforcing.
|
||||
*/
|
||||
public static boolean isSELinuxEnforced() {
|
||||
if (!sIsSELinuxEnabled) {
|
||||
return false;
|
||||
}
|
||||
boolean result = false;
|
||||
final File SELINUX_STATUS_FILE = new File("/sys/fs/selinux/enforce");
|
||||
if (SELINUX_STATUS_FILE.exists()) {
|
||||
try {
|
||||
FileInputStream fis = new FileInputStream(SELINUX_STATUS_FILE);
|
||||
int status = fis.read();
|
||||
switch (status) {
|
||||
case 49:
|
||||
result = true;
|
||||
break;
|
||||
case 48:
|
||||
result = false;
|
||||
break;
|
||||
default:
|
||||
XposedBridge.log("Unexpected byte " + status + " in /sys/fs/selinux/enforce");
|
||||
}
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
if (e.getMessage().contains("Permission denied")) {
|
||||
result = true;
|
||||
} else {
|
||||
XposedBridge.log("Failed to read SELinux status: " + e.getMessage());
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
// lsp: always enforcing
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -88,7 +55,7 @@ public final class SELinuxHelper {
|
|||
* @return A String representing the security context of the current process.
|
||||
*/
|
||||
public static String getContext() {
|
||||
return sIsSELinuxEnabled ? SELinux.getContext() : null;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -100,36 +67,9 @@ public final class SELinuxHelper {
|
|||
* @return An instance of the service.
|
||||
*/
|
||||
public static BaseService getAppDataFileService() {
|
||||
if (sServiceAppDataFile != null)
|
||||
return sServiceAppDataFile;
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
return sServiceAppDataFile;
|
||||
}
|
||||
|
||||
private static final BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TODO: SELinux status
|
||||
private static boolean sIsSELinuxEnabled = false;
|
||||
private static BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
|
||||
|
||||
/*package*/ public static void initOnce() {
|
||||
// ed: we assume all selinux policies have been added lively using magiskpolicy
|
||||
try {
|
||||
sIsSELinuxEnabled = SELinux.isSELinuxEnabled();
|
||||
} catch (NoClassDefFoundError ignored) {}
|
||||
}
|
||||
|
||||
/*package*/ static void initForProcess(String packageName) {
|
||||
// ed: sServiceAppDataFile has been initialized with default value
|
||||
// if (sIsSELinuxEnabled) {
|
||||
// if (packageName == null) { // Zygote
|
||||
// sServiceAppDataFile = new ZygoteService();
|
||||
// } else if (packageName.equals("android")) { //system_server
|
||||
// sServiceAppDataFile = BinderService.getService(BinderService.TARGET_APP);
|
||||
// } else { // app
|
||||
// sServiceAppDataFile = new DirectAccessService();
|
||||
// }
|
||||
// } else {
|
||||
// sServiceAppDataFile = new DirectAccessService();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,18 +41,15 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
import dalvik.system.InMemoryDexClassLoader;
|
||||
import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
import de.robv.android.xposed.callbacks.XCallback;
|
||||
import external.com.android.dx.DexMaker;
|
||||
import external.com.android.dx.TypeId;
|
||||
import io.github.lsposed.lspd.nativebridge.ModuleLogger;
|
||||
|
||||
import static de.robv.android.xposed.XposedHelpers.getIntField;
|
||||
import static de.robv.android.xposed.XposedHelpers.setObjectField;
|
||||
|
||||
/**
|
||||
|
|
@ -78,10 +75,6 @@ public final class XposedBridge {
|
|||
|
||||
/*package*/ static boolean isZygote = true; // ed: RuntimeInit.main() tool process not supported yet
|
||||
|
||||
private static int runtime = 2; // ed: only support art
|
||||
private static final int RUNTIME_DALVIK = 1;
|
||||
private static final int RUNTIME_ART = 2;
|
||||
|
||||
// This field is set "magically" on MIUI.
|
||||
/*package*/ static long BOOT_START_TIME;
|
||||
|
||||
|
|
@ -95,14 +88,6 @@ public final class XposedBridge {
|
|||
|
||||
private XposedBridge() {}
|
||||
|
||||
/** @hide */
|
||||
// protected static final class ToolEntryPoint {
|
||||
// protected static void main(String[] args) {
|
||||
// isZygote = false;
|
||||
// XposedBridge.main(args);
|
||||
// }
|
||||
// }
|
||||
|
||||
public static volatile ClassLoader dummyClassLoader = null;
|
||||
|
||||
@ApiSensitive(Level.MIDDLE)
|
||||
|
|
@ -143,15 +128,6 @@ public final class XposedBridge {
|
|||
}
|
||||
}
|
||||
|
||||
// private static boolean hadInitErrors() {
|
||||
// // ed: assuming never had errors
|
||||
// return false;
|
||||
// }
|
||||
// private static native int getRuntime();
|
||||
// /*package*/ static native boolean startsSystemServer();
|
||||
// /*package*/ static native String getStartClassName();
|
||||
// /*package*/ native static boolean initXResourcesNative();
|
||||
|
||||
/**
|
||||
* Returns the currently installed version of the Xposed framework.
|
||||
*/
|
||||
|
|
@ -227,28 +203,10 @@ public final class XposedBridge {
|
|||
callbacks.add(callback);
|
||||
|
||||
if (newMethod) {
|
||||
Class<?> declaringClass = hookMethod.getDeclaringClass();
|
||||
int slot;
|
||||
Class<?>[] parameterTypes;
|
||||
Class<?> returnType;
|
||||
if (runtime == RUNTIME_ART) {
|
||||
slot = 0;
|
||||
parameterTypes = null;
|
||||
returnType = null;
|
||||
} else if (hookMethod instanceof Method) {
|
||||
slot = getIntField(hookMethod, "slot");
|
||||
parameterTypes = ((Method) hookMethod).getParameterTypes();
|
||||
returnType = ((Method) hookMethod).getReturnType();
|
||||
} else {
|
||||
slot = getIntField(hookMethod, "slot");
|
||||
parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
|
||||
returnType = null;
|
||||
}
|
||||
|
||||
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
|
||||
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
|
||||
Member reflectMethod = LSPdConfigGlobal.getHookProvider().findMethodNative(hookMethod);
|
||||
if (reflectMethod != null) {
|
||||
hookMethodNative(reflectMethod, declaringClass, slot, additionalInfo);
|
||||
LSPdConfigGlobal.getHookProvider().hookMethod(reflectMethod, (AdditionalHookInfo) additionalInfo);
|
||||
} else {
|
||||
PendingHooks.recordPendingMethod((Method)hookMethod, additionalInfo);
|
||||
}
|
||||
|
|
@ -312,86 +270,6 @@ public final class XposedBridge {
|
|||
return unhooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called as a replacement for hooked methods.
|
||||
*/
|
||||
public static Object handleHookedMethod(Member method, long originalMethodId, Object additionalInfoObj,
|
||||
Object thisObject, Object[] args) throws Throwable {
|
||||
AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj;
|
||||
|
||||
Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
|
||||
final int callbacksLength = callbacksSnapshot.length;
|
||||
if (callbacksLength == 0) {
|
||||
try {
|
||||
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
|
||||
additionalInfo.returnType, thisObject, args);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
MethodHookParam param = new MethodHookParam();
|
||||
param.method = method;
|
||||
param.thisObject = thisObject;
|
||||
param.args = args;
|
||||
|
||||
// call "before method" callbacks
|
||||
int beforeIdx = 0;
|
||||
do {
|
||||
try {
|
||||
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
|
||||
// reset result (ignoring what the unexpectedly exiting callback did)
|
||||
param.setResult(null);
|
||||
param.returnEarly = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.returnEarly) {
|
||||
// skip remaining "before" callbacks and corresponding "after" callbacks
|
||||
beforeIdx++;
|
||||
break;
|
||||
}
|
||||
} while (++beforeIdx < callbacksLength);
|
||||
|
||||
// call original method if not requested otherwise
|
||||
if (!param.returnEarly) {
|
||||
try {
|
||||
param.setResult(invokeOriginalMethodNative(method, originalMethodId,
|
||||
additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
|
||||
} catch (InvocationTargetException e) {
|
||||
param.setThrowable(e.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
// call "after method" callbacks
|
||||
int afterIdx = beforeIdx - 1;
|
||||
do {
|
||||
Object lastResult = param.getResult();
|
||||
Throwable lastThrowable = param.getThrowable();
|
||||
|
||||
try {
|
||||
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
XposedBridge.log(t);
|
||||
|
||||
// reset to last result (ignoring what the unexpectedly exiting callback did)
|
||||
if (lastThrowable == null)
|
||||
param.setResult(lastResult);
|
||||
else
|
||||
param.setThrowable(lastThrowable);
|
||||
}
|
||||
} while (--afterIdx >= 0);
|
||||
|
||||
// return
|
||||
if (param.hasThrowable())
|
||||
throw param.getThrowable();
|
||||
else
|
||||
return param.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a callback to be executed when an app ("Android package") is loaded.
|
||||
*
|
||||
|
|
@ -428,51 +306,12 @@ public final class XposedBridge {
|
|||
}
|
||||
}
|
||||
|
||||
public static void clearInitPackageResources() {
|
||||
synchronized (sInitPackageResourcesCallbacks) {
|
||||
sInitPackageResourcesCallbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void hookInitZygote(XC_InitZygote callback) {
|
||||
synchronized (sInitZygoteCallbacks) {
|
||||
sInitZygoteCallbacks.add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public static void clearInitZygotes() {
|
||||
synchronized (sInitZygoteCallbacks) {
|
||||
sInitZygoteCallbacks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void callInitZygotes() {
|
||||
XCallback.callAll(new IXposedHookZygoteInit.StartupParam(sInitZygoteCallbacks));
|
||||
}
|
||||
|
||||
public static void clearAllCallbacks() {
|
||||
clearLoadedPackages();
|
||||
clearInitPackageResources();
|
||||
clearInitZygotes();
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept every call to the specified method and call a handler function instead.
|
||||
* @param method The method to intercept
|
||||
*/
|
||||
/*package*/ synchronized static void hookMethodNative(final Member method, Class<?> declaringClass,
|
||||
int slot, final Object additionalInfoObj) {
|
||||
LSPdConfigGlobal.getHookProvider().hookMethod(method, (AdditionalHookInfo) additionalInfoObj);
|
||||
}
|
||||
|
||||
private static Object invokeOriginalMethodNative(Member method, long methodId,
|
||||
Class<?>[] parameterTypes,
|
||||
Class<?> returnType,
|
||||
Object thisObject, Object[] args)
|
||||
throws Throwable {
|
||||
return LSPdConfigGlobal.getHookProvider().invokeOriginalMethod(method, methodId, thisObject, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Basically the same as {@link Method#invoke}, but calls the original method
|
||||
* as it was before the interception by Xposed. Also, access permissions are not checked.
|
||||
|
|
@ -504,34 +343,18 @@ public final class XposedBridge {
|
|||
args = EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
Class<?>[] parameterTypes;
|
||||
Class<?> returnType;
|
||||
if (runtime == RUNTIME_ART && (method instanceof Method || method instanceof Constructor)) {
|
||||
parameterTypes = null;
|
||||
returnType = null;
|
||||
} else if (method instanceof Method) {
|
||||
parameterTypes = ((Method) method).getParameterTypes();
|
||||
returnType = ((Method) method).getReturnType();
|
||||
} else if (method instanceof Constructor) {
|
||||
parameterTypes = ((Constructor<?>) method).getParameterTypes();
|
||||
returnType = null;
|
||||
} else {
|
||||
if (!(method instanceof Method) && !(method instanceof Constructor)) {
|
||||
throw new IllegalArgumentException("method must be of type Method or Constructor");
|
||||
}
|
||||
|
||||
long methodId = LSPdConfigGlobal.getHookProvider().getMethodId(method);
|
||||
return invokeOriginalMethodNative(method, methodId, parameterTypes, returnType, thisObject, args);
|
||||
return LSPdConfigGlobal.getHookProvider().invokeOriginalMethod(method, methodId, thisObject, args);
|
||||
}
|
||||
|
||||
private static void removeFinalFlagNative(Class clazz) {
|
||||
LSPdConfigGlobal.getHookProvider().removeFinalFlagNative(clazz);
|
||||
}
|
||||
|
||||
// /*package*/ static native void closeFilesBeforeForkNative();
|
||||
// /*package*/ static native void reopenFilesAfterForkNative();
|
||||
//
|
||||
// /*package*/ static native void invalidateCallersNative(Member[] methods);
|
||||
|
||||
/** @hide */
|
||||
public static final class CopyOnWriteSortedSet<E> {
|
||||
private transient volatile Object[] elements = EMPTY_ARRAY;
|
||||
|
|
@ -582,13 +405,9 @@ public final class XposedBridge {
|
|||
|
||||
public static class AdditionalHookInfo {
|
||||
public final CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
||||
public final Class<?>[] parameterTypes;
|
||||
public final Class<?> returnType;
|
||||
|
||||
private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks, Class<?>[] parameterTypes, Class<?> returnType) {
|
||||
private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks) {
|
||||
this.callbacks = callbacks;
|
||||
this.parameterTypes = parameterTypes;
|
||||
this.returnType = returnType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1645,70 +1645,6 @@ public final class XposedHelpers {
|
|||
}
|
||||
}
|
||||
|
||||
/*package*/ static boolean fileContains(File file, String str) throws IOException {
|
||||
// There are certainly more efficient algorithms (e.g. Boyer-Moore used in grep),
|
||||
// but the naive approach should be sufficient here.
|
||||
BufferedReader in = null;
|
||||
try {
|
||||
in = new BufferedReader(new FileReader(file));
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
if (line.contains(str)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} finally {
|
||||
closeSilently(in);
|
||||
}
|
||||
}
|
||||
|
||||
//#################################################################################################
|
||||
|
||||
/**
|
||||
* Returns the method that is overridden by the given method.
|
||||
* It returns {@code null} if the method doesn't override another method or if that method is
|
||||
* abstract, i.e. if this is the first implementation in the hierarchy.
|
||||
*/
|
||||
/*package*/ static Method getOverriddenMethod(Method method) {
|
||||
int modifiers = method.getModifiers();
|
||||
if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String name = method.getName();
|
||||
Class<?>[] parameters = method.getParameterTypes();
|
||||
Class<?> clazz = method.getDeclaringClass().getSuperclass();
|
||||
while (clazz != null) {
|
||||
try {
|
||||
Method superMethod = clazz.getDeclaredMethod(name, parameters);
|
||||
modifiers = superMethod.getModifiers();
|
||||
if (!Modifier.isPrivate(modifiers) && !Modifier.isAbstract(modifiers)) {
|
||||
return superMethod;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
clazz = clazz.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all methods which this class overrides.
|
||||
*/
|
||||
/*package*/ static Set<Method> getOverriddenMethods(Class<?> clazz) {
|
||||
Set<Method> methods = new HashSet<>();
|
||||
for (Method method : clazz.getDeclaredMethods()) {
|
||||
Method overridden = getOverriddenMethod(method);
|
||||
if (overridden != null) {
|
||||
methods.add(overridden);
|
||||
}
|
||||
}
|
||||
return methods;
|
||||
}
|
||||
|
||||
//#################################################################################################
|
||||
// TODO helpers for view traversing
|
||||
/*To make it easier, I will try and implement some more helpers:
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@
|
|||
|
||||
package de.robv.android.xposed;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.app.AndroidAppHelper;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.ResourcesImpl;
|
||||
import android.content.res.TypedArray;
|
||||
import android.content.res.XResources;
|
||||
import android.os.Build;
|
||||
|
|
@ -34,10 +34,10 @@ import android.util.Log;
|
|||
|
||||
import com.android.internal.os.ZygoteInit;
|
||||
|
||||
import hidden.HiddenApiBridge;
|
||||
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
|
@ -48,21 +48,18 @@ import java.lang.reflect.Method;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import dalvik.system.PathClassLoader;
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
import de.robv.android.xposed.callbacks.XCallback;
|
||||
import io.github.lsposed.lspd.nativebridge.NativeAPI;
|
||||
|
||||
import static de.robv.android.xposed.XposedBridge.hookAllConstructors;
|
||||
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
|
||||
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
|
||||
import static de.robv.android.xposed.XposedBridge.sInitZygoteCallbacks;
|
||||
|
|
@ -95,7 +92,7 @@ public final class XposedInit {
|
|||
* Hook some methods which we want to create an easier interface for developers.
|
||||
*/
|
||||
/*package*/
|
||||
public static void initForZygote(boolean isSystem) throws Throwable {
|
||||
public static void initForZygote() throws Throwable {
|
||||
// TODO Are these still needed for us?
|
||||
// MIUI
|
||||
if (findFieldIfExists(ZygoteInit.class, "BOOT_START_TIME") != null) {
|
||||
|
|
@ -147,11 +144,7 @@ public final class XposedInit {
|
|||
final ThreadLocal<Object> latestResKey = new ThreadLocal<>();
|
||||
final String createResourceMethod;
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 18) {
|
||||
classGTLR = ActivityThread.class;
|
||||
classResKey = Class.forName("android.app.ActivityThread$ResourcesKey");
|
||||
createResourceMethod = "getOrCreateResources";
|
||||
} else if (Build.VERSION.SDK_INT < 30) {
|
||||
if (Build.VERSION.SDK_INT < 30) {
|
||||
classGTLR = Class.forName("android.app.ResourcesManager");
|
||||
classResKey = Class.forName("android.content.res.ResourcesKey");
|
||||
createResourceMethod = "getOrCreateResources";
|
||||
|
|
@ -161,106 +154,32 @@ public final class XposedInit {
|
|||
createResourceMethod = "createResources";
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
hookAllMethods(classGTLR, createResourceMethod, 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);
|
||||
hookAllMethods(classGTLR, createResourceMethod, 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);
|
||||
String resDir = (String) getObjectField(param.args[resKeyIdx], "mResDir");
|
||||
XResources newRes = cloneToXResources(param, resDir);
|
||||
if (newRes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
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));
|
||||
}
|
||||
});
|
||||
|
||||
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() {
|
||||
|
|
@ -286,8 +205,7 @@ public final class XposedInit {
|
|||
// Replace system resources
|
||||
XResources systemRes = new XResources(
|
||||
(ClassLoader) XposedHelpers.getObjectField(Resources.getSystem(), "mClassLoader"));
|
||||
XposedHelpers.callMethod(systemRes, "setImpl", XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
|
||||
//systemRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
|
||||
HiddenApiBridge.Resources_setImpl(systemRes, (ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
|
||||
systemRes.initObject(null);
|
||||
setStaticObjectField(Resources.class, "mSystem", systemRes);
|
||||
|
||||
|
|
@ -305,8 +223,7 @@ public final class XposedInit {
|
|||
// Replace the returned resources with our subclass.
|
||||
XResources newRes = new XResources(
|
||||
(ClassLoader) XposedHelpers.getObjectField(param.getResult(), "mClassLoader"));
|
||||
XposedHelpers.callMethod(newRes, "setImpl", XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl"));
|
||||
//newRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl"));
|
||||
HiddenApiBridge.Resources_setImpl(newRes, (ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
|
||||
newRes.initObject(resDir);
|
||||
|
||||
// Invoke handleInitPackageResources().
|
||||
|
|
@ -322,11 +239,6 @@ public final class XposedInit {
|
|||
return newRes;
|
||||
}
|
||||
|
||||
private static boolean needsToCloseFilesForFork() {
|
||||
// ed: we always start to do our work after forking finishes
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to load all modules defined in <code>INSTALLER_DATA_BASE_DIR/conf/modules.list</code>
|
||||
*/
|
||||
|
|
@ -457,34 +369,25 @@ public final class XposedInit {
|
|||
}
|
||||
|
||||
final Object moduleInstance = moduleClass.newInstance();
|
||||
if (XposedBridge.isZygote) {
|
||||
if (moduleInstance instanceof IXposedHookZygoteInit) {
|
||||
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
||||
param.modulePath = apk;
|
||||
param.startsSystemServer = startsSystemServer;
|
||||
if (moduleInstance instanceof IXposedHookZygoteInit) {
|
||||
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
||||
param.modulePath = apk;
|
||||
param.startsSystemServer = startsSystemServer;
|
||||
|
||||
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
|
||||
(IXposedHookZygoteInit) moduleInstance, param));
|
||||
if (callInitZygote) {
|
||||
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleInstance instanceof IXposedHookLoadPackage)
|
||||
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
|
||||
(IXposedHookLoadPackage) moduleInstance, apk));
|
||||
|
||||
if (moduleInstance instanceof IXposedHookInitPackageResources)
|
||||
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
|
||||
(IXposedHookInitPackageResources) moduleInstance, apk));
|
||||
} else {
|
||||
if (moduleInstance instanceof IXposedHookCmdInit) {
|
||||
IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam();
|
||||
param.modulePath = apk;
|
||||
param.startClassName = startClassName;
|
||||
((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
|
||||
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
|
||||
(IXposedHookZygoteInit) moduleInstance, param));
|
||||
if (callInitZygote) {
|
||||
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleInstance instanceof IXposedHookLoadPackage)
|
||||
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper(
|
||||
(IXposedHookLoadPackage) moduleInstance, apk));
|
||||
|
||||
if (moduleInstance instanceof IXposedHookInitPackageResources)
|
||||
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper(
|
||||
(IXposedHookInitPackageResources) moduleInstance, apk));
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, " Failed to load class " + moduleClassName, t);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2020 EdXposed Contributors
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains the base classes for callbacks.
|
||||
*
|
||||
* <p>For historical reasons, {@link de.robv.android.xposed.XC_MethodHook} and
|
||||
* {@link de.robv.android.xposed.XC_MethodReplacement} are directly in the
|
||||
* {@code de.robv.android.xposed} package.
|
||||
*/
|
||||
package de.robv.android.xposed.callbacks;
|
||||
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2020 EdXposed Contributors
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains the main classes of the Xposed framework.
|
||||
*/
|
||||
package de.robv.android.xposed;
|
||||
|
|
@ -1,186 +0,0 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2020 EdXposed Contributors
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package de.robv.android.xposed.services;
|
||||
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/** @hide */
|
||||
public final class BinderService extends BaseService {
|
||||
public static final int TARGET_APP = 0;
|
||||
public static final int TARGET_SYSTEM = 1;
|
||||
|
||||
/**
|
||||
* Retrieve the binder service running in the specified context.
|
||||
* @param target Either {@link #TARGET_APP} or {@link #TARGET_SYSTEM}.
|
||||
* @return A reference to the service.
|
||||
* @throws IllegalStateException In case the service doesn't exist (should never happen).
|
||||
*/
|
||||
public static BinderService getService(int target) {
|
||||
if (target < 0 || target > sServices.length) {
|
||||
throw new IllegalArgumentException("Invalid service target " + target);
|
||||
}
|
||||
synchronized (sServices) {
|
||||
if (sServices[target] == null) {
|
||||
sServices[target] = new BinderService(target);
|
||||
}
|
||||
return sServices[target];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkFileAccess(String filename, int mode) {
|
||||
ensureAbsolutePath(filename);
|
||||
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeInterfaceToken(INTERFACE_TOKEN);
|
||||
data.writeString(filename);
|
||||
data.writeInt(mode);
|
||||
|
||||
try {
|
||||
mRemote.transact(ACCESS_FILE_TRANSACTION, data, reply, 0);
|
||||
} catch (RemoteException e) {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
return false;
|
||||
}
|
||||
|
||||
reply.readException();
|
||||
int result = reply.readInt();
|
||||
reply.recycle();
|
||||
data.recycle();
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResult statFile(String filename) throws IOException {
|
||||
ensureAbsolutePath(filename);
|
||||
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeInterfaceToken(INTERFACE_TOKEN);
|
||||
data.writeString(filename);
|
||||
|
||||
try {
|
||||
mRemote.transact(STAT_FILE_TRANSACTION, data, reply, 0);
|
||||
} catch (RemoteException e) {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
reply.readException();
|
||||
int errno = reply.readInt();
|
||||
if (errno != 0)
|
||||
throwCommonIOException(errno, null, filename, " while retrieving attributes for ");
|
||||
|
||||
long size = reply.readLong();
|
||||
long time = reply.readLong();
|
||||
reply.recycle();
|
||||
data.recycle();
|
||||
return new FileResult(size, time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] readFile(String filename) throws IOException {
|
||||
return readFile(filename, 0, 0, 0, 0).content;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
|
||||
return readFile(filename, 0, 0, previousSize, previousTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileResult readFile(String filename, int offset, int length,
|
||||
long previousSize, long previousTime) throws IOException {
|
||||
ensureAbsolutePath(filename);
|
||||
|
||||
Parcel data = Parcel.obtain();
|
||||
Parcel reply = Parcel.obtain();
|
||||
data.writeInterfaceToken(INTERFACE_TOKEN);
|
||||
data.writeString(filename);
|
||||
data.writeInt(offset);
|
||||
data.writeInt(length);
|
||||
data.writeLong(previousSize);
|
||||
data.writeLong(previousTime);
|
||||
|
||||
try {
|
||||
mRemote.transact(READ_FILE_TRANSACTION, data, reply, 0);
|
||||
} catch (RemoteException e) {
|
||||
data.recycle();
|
||||
reply.recycle();
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
reply.readException();
|
||||
int errno = reply.readInt();
|
||||
String errorMsg = reply.readString();
|
||||
long size = reply.readLong();
|
||||
long time = reply.readLong();
|
||||
byte[] content = reply.createByteArray();
|
||||
reply.recycle();
|
||||
data.recycle();
|
||||
|
||||
switch (errno) {
|
||||
case 0:
|
||||
return new FileResult(content, size, time);
|
||||
case 22: // EINVAL
|
||||
if (errorMsg != null) {
|
||||
IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
|
||||
if (offset == 0 && length == 0)
|
||||
throw new IOException(iae);
|
||||
else
|
||||
throw iae;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Offset " + offset + " / Length " + length
|
||||
+ " is out of range for " + filename + " with size " + size);
|
||||
}
|
||||
default:
|
||||
throwCommonIOException(errno, errorMsg, filename, " while reading ");
|
||||
throw new IllegalStateException(); // not reached
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
private static final String INTERFACE_TOKEN = "de.robv.android.xposed.IXposedService";
|
||||
|
||||
private static final int ACCESS_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
|
||||
private static final int STAT_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
|
||||
private static final int READ_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
|
||||
|
||||
private static final String[] SERVICE_NAMES = { "user.xposed.app", "user.xposed.system" };
|
||||
private static final BinderService[] sServices = new BinderService[2];
|
||||
private final IBinder mRemote;
|
||||
|
||||
private BinderService(int target) {
|
||||
IBinder binder = ServiceManager.getService(SERVICE_NAMES[target]);
|
||||
if (binder == null)
|
||||
throw new IllegalStateException("Service " + SERVICE_NAMES[target] + " does not exist");
|
||||
this.mRemote = binder;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2020 EdXposed Contributors
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package de.robv.android.xposed.services;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings("JniMissingFunction")
|
||||
public final class ZygoteService extends BaseService {
|
||||
@Override
|
||||
public native boolean checkFileAccess(String filename, int mode);
|
||||
|
||||
@Override
|
||||
public native FileResult statFile(String filename) throws IOException;
|
||||
|
||||
@Override
|
||||
public native byte[] readFile(String filename) throws IOException;
|
||||
|
||||
@Override
|
||||
// Just for completeness, we don't expect this to be called often in Zygote.
|
||||
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
|
||||
FileResult stat = statFile(filename);
|
||||
if (previousSize == stat.size && previousTime == stat.mtime)
|
||||
return stat;
|
||||
return new FileResult(readFile(filename), stat.size, stat.mtime);
|
||||
}
|
||||
|
||||
@Override
|
||||
// Just for completeness, we don't expect this to be called often in Zygote.
|
||||
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
|
||||
FileResult stat = statFile(filename);
|
||||
if (previousSize == stat.size && previousTime == stat.mtime)
|
||||
return stat;
|
||||
|
||||
// Shortcut for the simple case
|
||||
if (offset <= 0 && length <= 0)
|
||||
return new FileResult(readFile(filename), stat.size, stat.mtime);
|
||||
|
||||
// Check range
|
||||
if (offset > 0 && offset >= stat.size) {
|
||||
throw new IllegalArgumentException("offset " + offset + " >= size " + stat.size + " for " + filename);
|
||||
} else if (offset < 0) {
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
if (length > 0 && (offset + length) > stat.size) {
|
||||
throw new IllegalArgumentException("offset " + offset + " + length " + length + " > size " + stat.size + " for " + filename);
|
||||
} else if (length <= 0) {
|
||||
length = (int) (stat.size - offset);
|
||||
}
|
||||
|
||||
byte[] content = readFile(filename);
|
||||
return new FileResult(Arrays.copyOfRange(content, offset, offset + length), stat.size, stat.mtime);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2020 EdXposed Contributors
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
/**
|
||||
* Contains file access services provided by the Xposed framework.
|
||||
*/
|
||||
package de.robv.android.xposed.services;
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package de.robv.android.xposed.annotation;
|
||||
package io.github.lsposed.lspd.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
package de.robv.android.xposed.annotation;
|
||||
package io.github.lsposed.lspd.annotation;
|
||||
|
||||
public enum Level {
|
||||
LOW, MIDDLE, HIGH;
|
||||
|
|
@ -22,8 +22,8 @@ package io.github.lsposed.lspd.deopt;
|
|||
|
||||
import java.util.HashMap;
|
||||
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
|
||||
/**
|
||||
* Providing a whitelist of methods which are the callers of the target methods we want to hook.
|
||||
|
|
|
|||
|
|
@ -37,8 +37,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import de.robv.android.xposed.XposedInit;
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
|
||||
public abstract class BaseRouter implements Router {
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ public abstract class BaseRouter implements Router {
|
|||
return;
|
||||
}
|
||||
startBootstrapHook(isSystem, appDataDir);
|
||||
XposedInit.initForZygote(isSystem);
|
||||
XposedInit.initForZygote();
|
||||
} catch (Throwable t) {
|
||||
Utils.logE("error during Xposed initialization", t);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,6 @@ import io.github.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
|
|||
import io.github.lsposed.lspd.nativebridge.ModuleLogger;
|
||||
import io.github.lsposed.lspd.util.Utils;
|
||||
|
||||
import de.robv.android.xposed.SELinuxHelper;
|
||||
|
||||
import static io.github.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
|
||||
|
||||
public class NormalProxy extends BaseProxy {
|
||||
|
|
@ -51,7 +49,6 @@ public class NormalProxy extends BaseProxy {
|
|||
private void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
|
||||
// init logger
|
||||
ModuleLogger.initLogger(serviceClient.getModuleLogger());
|
||||
SELinuxHelper.initOnce();
|
||||
mRouter.initResourcesHook();
|
||||
mRouter.prepare(isSystem);
|
||||
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ import dalvik.system.PathClassLoader;
|
|||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
|
||||
@ApiSensitive(Level.LOW)
|
||||
public class ClassLoaderUtils {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ import java.lang.reflect.Method;
|
|||
import java.lang.reflect.Modifier;
|
||||
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
|
||||
public class ClassUtils {
|
||||
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import java.lang.reflect.Field;
|
|||
|
||||
import dalvik.system.BaseDexClassLoader;
|
||||
import dalvik.system.DexClassLoader;
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
|
||||
/**
|
||||
* For 6.0 only.
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
|
||||
@ApiSensitive(Level.LOW)
|
||||
public class ProcessUtils {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import android.util.Log;
|
|||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
|
||||
@ApiSensitive(Level.LOW)
|
||||
public final class Unsafe {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import android.util.Log;
|
|||
import io.github.lsposed.lspd.BuildConfig;
|
||||
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
||||
import de.robv.android.xposed.annotation.Level;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
|
||||
|
||||
public class Utils {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
package hidden;
|
||||
|
||||
import android.content.res.AssetManager;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.ResourcesImpl;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
|
||||
|
|
@ -31,4 +33,8 @@ public class HiddenApiBridge {
|
|||
public static IBinder Binder_allowBlocking(IBinder binder) {
|
||||
return Binder.allowBlocking(binder);
|
||||
}
|
||||
|
||||
public static void Resources_setImpl(Resources resources, ResourcesImpl impl) {
|
||||
resources.setImpl(impl);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,4 +10,9 @@ public class Resources {
|
|||
public Resources(ClassLoader classLoader) {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
public void setImpl(ResourcesImpl impl) {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
package android.os;
|
||||
|
||||
public class SELinux {
|
||||
public static final String getContext() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
public static final boolean isSELinuxEnabled() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
|
||||
public static final boolean isSELinuxEnforced() {
|
||||
throw new UnsupportedOperationException("STUB");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue