[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;
|
private static final boolean HAS_THEME_CONFIG_PARAMETER;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
CLASS_RESOURCES_KEY = (Build.VERSION.SDK_INT < 19) ?
|
CLASS_RESOURCES_KEY = findClass("android.content.res.ResourcesKey", null);
|
||||||
findClass("android.app.ActivityThread$ResourcesKey", null)
|
|
||||||
: findClass("android.content.res.ResourcesKey", null);
|
|
||||||
|
|
||||||
HAS_IS_THEMEABLE = findFieldIfExists(CLASS_RESOURCES_KEY, "mIsThemeable") != null;
|
HAS_IS_THEMEABLE = findFieldIfExists(CLASS_RESOURCES_KEY, "mIsThemeable") != null;
|
||||||
HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && Build.VERSION.SDK_INT >= 21
|
HAS_THEME_CONFIG_PARAMETER = HAS_IS_THEMEABLE && findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
|
||||||
&& findMethodExactIfExists("android.app.ResourcesManager", null, "getThemeConfig") != null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
private static Map<Object, WeakReference> getResourcesMap(ActivityThread activityThread) {
|
private static Map<Object, WeakReference> getResourcesMap(ActivityThread activityThread) {
|
||||||
if (Build.VERSION.SDK_INT >= 24) {
|
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
|
||||||
Object resourcesManager = getObjectField(activityThread, "mResourcesManager");
|
return (Map) getObjectField(resourcesManager, "mResourceImpls");
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For SDK 24+ */
|
/* For SDK 24+ */
|
||||||
|
|
@ -144,27 +91,13 @@ public final class AndroidAppHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
Object resourcesKey;
|
Object resourcesKey;
|
||||||
if (Build.VERSION.SDK_INT >= 24) {
|
CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class);
|
||||||
CompatibilityInfo compatInfo = (CompatibilityInfo) newInstance(CompatibilityInfo.class);
|
setFloatField(compatInfo, "applicationScale", resources.hashCode());
|
||||||
setFloatField(compatInfo, "applicationScale", resources.hashCode());
|
resourcesKey = createResourcesKey(resDir, null, null, null, Display.DEFAULT_DISPLAY, null, compatInfo);
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourcesKey != null) {
|
if (resourcesKey != null) {
|
||||||
if (Build.VERSION.SDK_INT >= 24) {
|
Object resImpl = getObjectField(resources, "mResourcesImpl");
|
||||||
Object resImpl = getObjectField(resources, "mResourcesImpl");
|
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl));
|
||||||
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resImpl));
|
|
||||||
} else {
|
|
||||||
getResourcesMap(thread).put(resourcesKey, new WeakReference<>(resources));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,11 +119,7 @@ public class XResources extends XResourcesSuperClass {
|
||||||
|
|
||||||
if (resDir != null) {
|
if (resDir != null) {
|
||||||
synchronized (sReplacementsCacheMap) {
|
synchronized (sReplacementsCacheMap) {
|
||||||
mReplacementsCache = sReplacementsCacheMap.get(resDir);
|
mReplacementsCache = sReplacementsCacheMap.computeIfAbsent(resDir, k -> new byte[128]);
|
||||||
if (mReplacementsCache == null) {
|
|
||||||
mReplacementsCache = new byte[128];
|
|
||||||
sReplacementsCacheMap.put(resDir, mReplacementsCache);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,14 +180,10 @@ public class XResources extends XResourcesSuperClass {
|
||||||
return packageName;
|
return packageName;
|
||||||
|
|
||||||
PackageParser.PackageLite pkgInfo;
|
PackageParser.PackageLite pkgInfo;
|
||||||
if (Build.VERSION.SDK_INT >= 21) {
|
try {
|
||||||
try {
|
pkgInfo = PackageParser.parsePackageLite(new File(resDir), 0);
|
||||||
pkgInfo = PackageParser.parsePackageLite(new File(resDir), 0);
|
} catch (PackageParserException e) {
|
||||||
} catch (PackageParserException e) {
|
throw new IllegalStateException("Could not determine package name for " + resDir, e);
|
||||||
throw new IllegalStateException("Could not determine package name for " + resDir, e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
pkgInfo = PackageParser.parsePackageLite(resDir, 0);
|
|
||||||
}
|
}
|
||||||
if (pkgInfo != null && pkgInfo.packageName != null) {
|
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");
|
||||||
|
|
@ -265,7 +257,7 @@ public class XResources extends XResourcesSuperClass {
|
||||||
XMLInstanceDetails details = (XMLInstanceDetails) param.getObjectExtra(EXTRA_XML_INSTANCE_DETAILS);
|
XMLInstanceDetails details = (XMLInstanceDetails) param.getObjectExtra(EXTRA_XML_INSTANCE_DETAILS);
|
||||||
if (details != null) {
|
if (details != null) {
|
||||||
LayoutInflatedParam liparam = new LayoutInflatedParam(details.callbacks);
|
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.view = group.getChildAt(group.getChildCount() - 1);
|
||||||
liparam.resNames = details.resNames;
|
liparam.resNames = details.resNames;
|
||||||
liparam.variant = details.variant;
|
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, Context.class,
|
||||||
findAndHookMethod(LayoutInflater.class, "parseInclude", XmlPullParser.class, View.class,
|
View.class, AttributeSet.class, parseIncludeHook);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -662,9 +646,7 @@ public class XResources extends XResourcesSuperClass {
|
||||||
XmlResourceParser result = repRes.getAnimation(repId);
|
XmlResourceParser result = repRes.getAnimation(repId);
|
||||||
|
|
||||||
if (!loadedFromCache) {
|
if (!loadedFromCache) {
|
||||||
long parseState = (Build.VERSION.SDK_INT >= 21)
|
long parseState = getLongField(result, "mParseState");
|
||||||
? getLongField(result, "mParseState")
|
|
||||||
: getIntField(result, "mParseState");
|
|
||||||
rewriteXmlReferencesNative(parseState, this, repRes);
|
rewriteXmlReferencesNative(parseState, this, repRes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -924,9 +906,7 @@ public class XResources extends XResourcesSuperClass {
|
||||||
result = repRes.getLayout(repId);
|
result = repRes.getLayout(repId);
|
||||||
|
|
||||||
if (!loadedFromCache) {
|
if (!loadedFromCache) {
|
||||||
long parseState = (Build.VERSION.SDK_INT >= 21)
|
long parseState = getLongField(result, "mParseState");
|
||||||
? getLongField(result, "mParseState")
|
|
||||||
: getIntField(result, "mParseState");
|
|
||||||
rewriteXmlReferencesNative(parseState, this, repRes);
|
rewriteXmlReferencesNative(parseState, this, repRes);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1080,9 +1060,7 @@ public class XResources extends XResourcesSuperClass {
|
||||||
XmlResourceParser result = repRes.getXml(repId);
|
XmlResourceParser result = repRes.getXml(repId);
|
||||||
|
|
||||||
if (!loadedFromCache) {
|
if (!loadedFromCache) {
|
||||||
long parseState = (Build.VERSION.SDK_INT >= 21)
|
long parseState = getLongField(result, "mParseState");
|
||||||
? getLongField(result, "mParseState")
|
|
||||||
: getIntField(result, "mParseState");
|
|
||||||
rewriteXmlReferencesNative(parseState, this, repRes);
|
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.lang.reflect.Method;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
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;
|
import static io.github.lsposed.lspd.nativebridge.PendingHooks.recordPendingMethodNative;
|
||||||
|
|
||||||
public final class PendingHooks {
|
public final class PendingHooks {
|
||||||
|
|
@ -38,7 +38,7 @@ public final class PendingHooks {
|
||||||
public synchronized static void hookPendingMethod(Class<?> clazz) {
|
public synchronized static void hookPendingMethod(Class<?> clazz) {
|
||||||
if (sPendingHooks.containsKey(clazz)) {
|
if (sPendingHooks.containsKey(clazz)) {
|
||||||
for (Map.Entry<Member, XposedBridge.AdditionalHookInfo> hook : sPendingHooks.get(clazz).entrySet()) {
|
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);
|
sPendingHooks.remove(clazz);
|
||||||
}
|
}
|
||||||
|
|
@ -47,13 +47,7 @@ public final class PendingHooks {
|
||||||
public synchronized static void recordPendingMethod(Method hookMethod,
|
public synchronized static void recordPendingMethod(Method hookMethod,
|
||||||
XposedBridge.AdditionalHookInfo additionalInfo) {
|
XposedBridge.AdditionalHookInfo additionalInfo) {
|
||||||
ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> pending =
|
ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> pending =
|
||||||
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(),
|
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(), aClass -> new ConcurrentHashMap<>());
|
||||||
new Function<Class<?>, ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo>>() {
|
|
||||||
@Override
|
|
||||||
public ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> apply(Class<?> aClass) {
|
|
||||||
return new ConcurrentHashMap<>();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pending.put(hookMethod, additionalInfo);
|
pending.put(hookMethod, additionalInfo);
|
||||||
recordPendingMethodNative(hookMethod, hookMethod.getDeclaringClass());
|
recordPendingMethodNative(hookMethod, hookMethod.getDeclaringClass());
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,6 @@
|
||||||
|
|
||||||
package de.robv.android.xposed;
|
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.BaseService;
|
||||||
import de.robv.android.xposed.services.DirectAccessService;
|
import de.robv.android.xposed.services.DirectAccessService;
|
||||||
|
|
||||||
|
|
@ -41,7 +35,8 @@ public final class SELinuxHelper {
|
||||||
* @return A boolean indicating whether SELinux is enabled.
|
* @return A boolean indicating whether SELinux is enabled.
|
||||||
*/
|
*/
|
||||||
public static boolean isSELinuxEnabled() {
|
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.
|
* @return A boolean indicating whether SELinux is enforcing.
|
||||||
*/
|
*/
|
||||||
public static boolean isSELinuxEnforced() {
|
public static boolean isSELinuxEnforced() {
|
||||||
if (!sIsSELinuxEnabled) {
|
// lsp: always enforcing
|
||||||
return false;
|
return true;
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -88,7 +55,7 @@ public final class SELinuxHelper {
|
||||||
* @return A String representing the security context of the current process.
|
* @return A String representing the security context of the current process.
|
||||||
*/
|
*/
|
||||||
public static String getContext() {
|
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.
|
* @return An instance of the service.
|
||||||
*/
|
*/
|
||||||
public static BaseService getAppDataFileService() {
|
public static BaseService getAppDataFileService() {
|
||||||
if (sServiceAppDataFile != null)
|
return sServiceAppDataFile;
|
||||||
return sServiceAppDataFile;
|
}
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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 java.util.Set;
|
||||||
|
|
||||||
import dalvik.system.InMemoryDexClassLoader;
|
import dalvik.system.InMemoryDexClassLoader;
|
||||||
import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
|
||||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
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.DexMaker;
|
||||||
import external.com.android.dx.TypeId;
|
import external.com.android.dx.TypeId;
|
||||||
import io.github.lsposed.lspd.nativebridge.ModuleLogger;
|
import io.github.lsposed.lspd.nativebridge.ModuleLogger;
|
||||||
|
|
||||||
import static de.robv.android.xposed.XposedHelpers.getIntField;
|
|
||||||
import static de.robv.android.xposed.XposedHelpers.setObjectField;
|
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
|
/*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.
|
// This field is set "magically" on MIUI.
|
||||||
/*package*/ static long BOOT_START_TIME;
|
/*package*/ static long BOOT_START_TIME;
|
||||||
|
|
||||||
|
|
@ -95,14 +88,6 @@ public final class XposedBridge {
|
||||||
|
|
||||||
private 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;
|
public static volatile ClassLoader dummyClassLoader = null;
|
||||||
|
|
||||||
@ApiSensitive(Level.MIDDLE)
|
@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.
|
* Returns the currently installed version of the Xposed framework.
|
||||||
*/
|
*/
|
||||||
|
|
@ -227,28 +203,10 @@ public final class XposedBridge {
|
||||||
callbacks.add(callback);
|
callbacks.add(callback);
|
||||||
|
|
||||||
if (newMethod) {
|
if (newMethod) {
|
||||||
Class<?> declaringClass = hookMethod.getDeclaringClass();
|
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
|
||||||
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);
|
|
||||||
Member reflectMethod = LSPdConfigGlobal.getHookProvider().findMethodNative(hookMethod);
|
Member reflectMethod = LSPdConfigGlobal.getHookProvider().findMethodNative(hookMethod);
|
||||||
if (reflectMethod != null) {
|
if (reflectMethod != null) {
|
||||||
hookMethodNative(reflectMethod, declaringClass, slot, additionalInfo);
|
LSPdConfigGlobal.getHookProvider().hookMethod(reflectMethod, (AdditionalHookInfo) additionalInfo);
|
||||||
} else {
|
} else {
|
||||||
PendingHooks.recordPendingMethod((Method)hookMethod, additionalInfo);
|
PendingHooks.recordPendingMethod((Method)hookMethod, additionalInfo);
|
||||||
}
|
}
|
||||||
|
|
@ -312,86 +270,6 @@ public final class XposedBridge {
|
||||||
return unhooks;
|
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.
|
* 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) {
|
public static void hookInitZygote(XC_InitZygote callback) {
|
||||||
synchronized (sInitZygoteCallbacks) {
|
synchronized (sInitZygoteCallbacks) {
|
||||||
sInitZygoteCallbacks.add(callback);
|
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
|
* 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.
|
* 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;
|
args = EMPTY_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?>[] parameterTypes;
|
if (!(method instanceof Method) && !(method instanceof Constructor)) {
|
||||||
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 {
|
|
||||||
throw new IllegalArgumentException("method must be of type Method or Constructor");
|
throw new IllegalArgumentException("method must be of type Method or Constructor");
|
||||||
}
|
}
|
||||||
|
|
||||||
long methodId = LSPdConfigGlobal.getHookProvider().getMethodId(method);
|
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) {
|
private static void removeFinalFlagNative(Class clazz) {
|
||||||
LSPdConfigGlobal.getHookProvider().removeFinalFlagNative(clazz);
|
LSPdConfigGlobal.getHookProvider().removeFinalFlagNative(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /*package*/ static native void closeFilesBeforeForkNative();
|
|
||||||
// /*package*/ static native void reopenFilesAfterForkNative();
|
|
||||||
//
|
|
||||||
// /*package*/ static native void invalidateCallersNative(Member[] methods);
|
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public static final class CopyOnWriteSortedSet<E> {
|
public static final class CopyOnWriteSortedSet<E> {
|
||||||
private transient volatile Object[] elements = EMPTY_ARRAY;
|
private transient volatile Object[] elements = EMPTY_ARRAY;
|
||||||
|
|
@ -582,13 +405,9 @@ public final class XposedBridge {
|
||||||
|
|
||||||
public static class AdditionalHookInfo {
|
public static class AdditionalHookInfo {
|
||||||
public final CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
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.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
|
// TODO helpers for view traversing
|
||||||
/*To make it easier, I will try and implement some more helpers:
|
/*To make it easier, I will try and implement some more helpers:
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,10 @@
|
||||||
|
|
||||||
package de.robv.android.xposed;
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
import android.app.ActivityThread;
|
|
||||||
import android.app.AndroidAppHelper;
|
import android.app.AndroidAppHelper;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.ResourcesImpl;
|
||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.content.res.XResources;
|
import android.content.res.XResources;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
@ -34,10 +34,10 @@ import android.util.Log;
|
||||||
|
|
||||||
import com.android.internal.os.ZygoteInit;
|
import com.android.internal.os.ZygoteInit;
|
||||||
|
|
||||||
|
import hidden.HiddenApiBridge;
|
||||||
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
|
@ -48,21 +48,18 @@ import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import dalvik.system.PathClassLoader;
|
import dalvik.system.PathClassLoader;
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||||
import de.robv.android.xposed.callbacks.XCallback;
|
import de.robv.android.xposed.callbacks.XCallback;
|
||||||
import io.github.lsposed.lspd.nativebridge.NativeAPI;
|
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.hookAllMethods;
|
||||||
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
|
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
|
||||||
import static de.robv.android.xposed.XposedBridge.sInitZygoteCallbacks;
|
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.
|
* Hook some methods which we want to create an easier interface for developers.
|
||||||
*/
|
*/
|
||||||
/*package*/
|
/*package*/
|
||||||
public static void initForZygote(boolean isSystem) throws Throwable {
|
public static void initForZygote() throws Throwable {
|
||||||
// TODO Are these still needed for us?
|
// TODO Are these still needed for us?
|
||||||
// MIUI
|
// MIUI
|
||||||
if (findFieldIfExists(ZygoteInit.class, "BOOT_START_TIME") != null) {
|
if (findFieldIfExists(ZygoteInit.class, "BOOT_START_TIME") != null) {
|
||||||
|
|
@ -147,11 +144,7 @@ public final class XposedInit {
|
||||||
final ThreadLocal<Object> latestResKey = new ThreadLocal<>();
|
final ThreadLocal<Object> latestResKey = new ThreadLocal<>();
|
||||||
final String createResourceMethod;
|
final String createResourceMethod;
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT <= 18) {
|
if (Build.VERSION.SDK_INT < 30) {
|
||||||
classGTLR = ActivityThread.class;
|
|
||||||
classResKey = Class.forName("android.app.ActivityThread$ResourcesKey");
|
|
||||||
createResourceMethod = "getOrCreateResources";
|
|
||||||
} else if (Build.VERSION.SDK_INT < 30) {
|
|
||||||
classGTLR = Class.forName("android.app.ResourcesManager");
|
classGTLR = Class.forName("android.app.ResourcesManager");
|
||||||
classResKey = Class.forName("android.content.res.ResourcesKey");
|
classResKey = Class.forName("android.content.res.ResourcesKey");
|
||||||
createResourceMethod = "getOrCreateResources";
|
createResourceMethod = "getOrCreateResources";
|
||||||
|
|
@ -161,106 +154,32 @@ public final class XposedInit {
|
||||||
createResourceMethod = "createResources";
|
createResourceMethod = "createResources";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 24) {
|
hookAllMethods(classGTLR, createResourceMethod, new XC_MethodHook() {
|
||||||
hookAllMethods(classGTLR, createResourceMethod, new XC_MethodHook() {
|
@Override
|
||||||
@Override
|
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
||||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
// At least on OnePlus 5, the method has an additional parameter compared to AOSP.
|
||||||
// At least on OnePlus 5, the method has an additional parameter compared to AOSP.
|
final int activityTokenIdx = getParameterIndexByType(param.method, IBinder.class);
|
||||||
final int activityTokenIdx = getParameterIndexByType(param.method, IBinder.class);
|
final int resKeyIdx = getParameterIndexByType(param.method, classResKey);
|
||||||
final int resKeyIdx = getParameterIndexByType(param.method, classResKey);
|
|
||||||
|
|
||||||
String resDir = (String) getObjectField(param.args[resKeyIdx], "mResDir");
|
String resDir = (String) getObjectField(param.args[resKeyIdx], "mResDir");
|
||||||
XResources newRes = cloneToXResources(param, resDir);
|
XResources newRes = cloneToXResources(param, resDir);
|
||||||
if (newRes == null) {
|
if (newRes == null) {
|
||||||
return;
|
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
|
Object activityToken = param.args[activityTokenIdx];
|
||||||
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
|
synchronized (param.thisObject) {
|
||||||
Object key = latestResKey.get();
|
ArrayList<WeakReference<Resources>> resourceReferences;
|
||||||
if (key == null) {
|
if (activityToken != null) {
|
||||||
return;
|
Object activityResources = callMethod(param.thisObject, "getOrCreateActivityResourcesStructLocked", activityToken);
|
||||||
}
|
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(activityResources, "activityResources");
|
||||||
latestResKey.set(null);
|
} else {
|
||||||
|
resourceReferences = (ArrayList<WeakReference<Resources>>) getObjectField(param.thisObject, "mResourceReferences");
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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,
|
findAndHookMethod(TypedArray.class, "obtain", Resources.class, int.class,
|
||||||
new XC_MethodHook() {
|
new XC_MethodHook() {
|
||||||
|
|
@ -286,8 +205,7 @@ public final class XposedInit {
|
||||||
// Replace system resources
|
// Replace system resources
|
||||||
XResources systemRes = new XResources(
|
XResources systemRes = new XResources(
|
||||||
(ClassLoader) XposedHelpers.getObjectField(Resources.getSystem(), "mClassLoader"));
|
(ClassLoader) XposedHelpers.getObjectField(Resources.getSystem(), "mClassLoader"));
|
||||||
XposedHelpers.callMethod(systemRes, "setImpl", XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
|
HiddenApiBridge.Resources_setImpl(systemRes, (ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
|
||||||
//systemRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
|
|
||||||
systemRes.initObject(null);
|
systemRes.initObject(null);
|
||||||
setStaticObjectField(Resources.class, "mSystem", systemRes);
|
setStaticObjectField(Resources.class, "mSystem", systemRes);
|
||||||
|
|
||||||
|
|
@ -305,8 +223,7 @@ public final class XposedInit {
|
||||||
// Replace the returned resources with our subclass.
|
// Replace the returned resources with our subclass.
|
||||||
XResources newRes = new XResources(
|
XResources newRes = new XResources(
|
||||||
(ClassLoader) XposedHelpers.getObjectField(param.getResult(), "mClassLoader"));
|
(ClassLoader) XposedHelpers.getObjectField(param.getResult(), "mClassLoader"));
|
||||||
XposedHelpers.callMethod(newRes, "setImpl", XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl"));
|
HiddenApiBridge.Resources_setImpl(newRes, (ResourcesImpl) XposedHelpers.getObjectField(Resources.getSystem(), "mResourcesImpl"));
|
||||||
//newRes.setImpl((ResourcesImpl) XposedHelpers.getObjectField(param.getResult(), "mResourcesImpl"));
|
|
||||||
newRes.initObject(resDir);
|
newRes.initObject(resDir);
|
||||||
|
|
||||||
// Invoke handleInitPackageResources().
|
// Invoke handleInitPackageResources().
|
||||||
|
|
@ -322,11 +239,6 @@ public final class XposedInit {
|
||||||
return newRes;
|
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>
|
* 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();
|
final Object moduleInstance = moduleClass.newInstance();
|
||||||
if (XposedBridge.isZygote) {
|
if (moduleInstance instanceof IXposedHookZygoteInit) {
|
||||||
if (moduleInstance instanceof IXposedHookZygoteInit) {
|
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
||||||
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
|
param.modulePath = apk;
|
||||||
param.modulePath = apk;
|
param.startsSystemServer = startsSystemServer;
|
||||||
param.startsSystemServer = startsSystemServer;
|
|
||||||
|
|
||||||
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
|
XposedBridge.hookInitZygote(new IXposedHookZygoteInit.Wrapper(
|
||||||
(IXposedHookZygoteInit) moduleInstance, param));
|
(IXposedHookZygoteInit) moduleInstance, param));
|
||||||
if (callInitZygote) {
|
if (callInitZygote) {
|
||||||
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
|
((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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
} catch (Throwable t) {
|
||||||
Log.e(TAG, " Failed to load class " + moduleClassName, t);
|
Log.e(TAG, " Failed to load class " + moduleClassName, t);
|
||||||
return false;
|
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
|
* 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.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
* Copyright (C) 2021 LSPosed Contributors
|
* Copyright (C) 2021 LSPosed Contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package de.robv.android.xposed.annotation;
|
package io.github.lsposed.lspd.annotation;
|
||||||
|
|
||||||
public enum Level {
|
public enum Level {
|
||||||
LOW, MIDDLE, HIGH;
|
LOW, MIDDLE, HIGH;
|
||||||
|
|
@ -22,8 +22,8 @@ package io.github.lsposed.lspd.deopt;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Providing a whitelist of methods which are the callers of the target methods we want to hook.
|
* 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.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
import de.robv.android.xposed.XposedInit;
|
import de.robv.android.xposed.XposedInit;
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
|
|
||||||
public abstract class BaseRouter implements Router {
|
public abstract class BaseRouter implements Router {
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ public abstract class BaseRouter implements Router {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
startBootstrapHook(isSystem, appDataDir);
|
startBootstrapHook(isSystem, appDataDir);
|
||||||
XposedInit.initForZygote(isSystem);
|
XposedInit.initForZygote();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Utils.logE("error during Xposed initialization", 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.nativebridge.ModuleLogger;
|
||||||
import io.github.lsposed.lspd.util.Utils;
|
import io.github.lsposed.lspd.util.Utils;
|
||||||
|
|
||||||
import de.robv.android.xposed.SELinuxHelper;
|
|
||||||
|
|
||||||
import static io.github.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
|
import static io.github.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
|
||||||
|
|
||||||
public class NormalProxy extends BaseProxy {
|
public class NormalProxy extends BaseProxy {
|
||||||
|
|
@ -51,7 +49,6 @@ public class NormalProxy extends BaseProxy {
|
||||||
private void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
|
private void forkPostCommon(boolean isSystem, String appDataDir, String niceName) {
|
||||||
// init logger
|
// init logger
|
||||||
ModuleLogger.initLogger(serviceClient.getModuleLogger());
|
ModuleLogger.initLogger(serviceClient.getModuleLogger());
|
||||||
SELinuxHelper.initOnce();
|
|
||||||
mRouter.initResourcesHook();
|
mRouter.initResourcesHook();
|
||||||
mRouter.prepare(isSystem);
|
mRouter.prepare(isSystem);
|
||||||
PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote
|
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.XC_MethodHook;
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
|
|
||||||
@ApiSensitive(Level.LOW)
|
@ApiSensitive(Level.LOW)
|
||||||
public class ClassLoaderUtils {
|
public class ClassLoaderUtils {
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
|
|
||||||
public class ClassUtils {
|
public class ClassUtils {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ import java.lang.reflect.Field;
|
||||||
|
|
||||||
import dalvik.system.BaseDexClassLoader;
|
import dalvik.system.BaseDexClassLoader;
|
||||||
import dalvik.system.DexClassLoader;
|
import dalvik.system.DexClassLoader;
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For 6.0 only.
|
* For 6.0 only.
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
|
|
||||||
@ApiSensitive(Level.LOW)
|
@ApiSensitive(Level.LOW)
|
||||||
public class ProcessUtils {
|
public class ProcessUtils {
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ import android.util.Log;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
|
|
||||||
@ApiSensitive(Level.LOW)
|
@ApiSensitive(Level.LOW)
|
||||||
public final class Unsafe {
|
public final class Unsafe {
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ import android.util.Log;
|
||||||
import io.github.lsposed.lspd.BuildConfig;
|
import io.github.lsposed.lspd.BuildConfig;
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
import de.robv.android.xposed.annotation.ApiSensitive;
|
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||||
import de.robv.android.xposed.annotation.Level;
|
import io.github.lsposed.lspd.annotation.Level;
|
||||||
|
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@
|
||||||
package hidden;
|
package hidden;
|
||||||
|
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.ResourcesImpl;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
|
||||||
|
|
@ -31,4 +33,8 @@ public class HiddenApiBridge {
|
||||||
public static IBinder Binder_allowBlocking(IBinder binder) {
|
public static IBinder Binder_allowBlocking(IBinder binder) {
|
||||||
return Binder.allowBlocking(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) {
|
public Resources(ClassLoader classLoader) {
|
||||||
throw new UnsupportedOperationException("STUB");
|
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