Fix and refactor DexParser implementation (#509)
In the LSPosedDexParser constructor, the methodIds array is allocated correctly, but the loop condition is wrong: its length is divided by 3 twice. We rewrite this class in Kotlin, marking the first commit of refactoring LSPosed into Vector. Co-authored-by: Willian Wang <git@willian.wang>
This commit is contained in:
parent
402d3984d3
commit
4cec46a074
|
|
@ -20,6 +20,7 @@
|
||||||
import com.android.build.api.dsl.ApplicationDefaultConfig
|
import com.android.build.api.dsl.ApplicationDefaultConfig
|
||||||
import com.android.build.api.dsl.CommonExtension
|
import com.android.build.api.dsl.CommonExtension
|
||||||
import com.android.build.gradle.api.AndroidBasePlugin
|
import com.android.build.gradle.api.AndroidBasePlugin
|
||||||
|
import com.ncorti.ktfmt.gradle.tasks.KtfmtFormatTask
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.lsplugin.cmaker)
|
alias(libs.plugins.lsplugin.cmaker)
|
||||||
|
|
@ -27,6 +28,8 @@ plugins {
|
||||||
alias(libs.plugins.agp.lib) apply false
|
alias(libs.plugins.agp.lib) apply false
|
||||||
alias(libs.plugins.agp.app) apply false
|
alias(libs.plugins.agp.app) apply false
|
||||||
alias(libs.plugins.nav.safeargs) apply false
|
alias(libs.plugins.nav.safeargs) apply false
|
||||||
|
alias(libs.plugins.kotlin) apply false
|
||||||
|
alias(libs.plugins.ktfmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmaker {
|
cmaker {
|
||||||
|
|
@ -119,3 +122,11 @@ subprojects {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.register<KtfmtFormatTask>("format") {
|
||||||
|
source = project.fileTree(rootDir)
|
||||||
|
include("*.gradle.kts", "*/build.gradle.kts")
|
||||||
|
dependsOn(":xposed:ktfmtFormat")
|
||||||
|
}
|
||||||
|
|
||||||
|
ktfmt { kotlinLangStyle() }
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ dependencies {
|
||||||
implementation(projects.hiddenapi.bridge)
|
implementation(projects.hiddenapi.bridge)
|
||||||
implementation(projects.services.daemonService)
|
implementation(projects.services.daemonService)
|
||||||
implementation(projects.services.managerService)
|
implementation(projects.services.managerService)
|
||||||
|
implementation(projects.xposed)
|
||||||
compileOnly(libs.androidx.annotation)
|
compileOnly(libs.androidx.annotation)
|
||||||
compileOnly(projects.hiddenapi.stubs)
|
compileOnly(projects.hiddenapi.stubs)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.lsposed.lspd.core.BuildConfig;
|
import org.lsposed.lspd.core.BuildConfig;
|
||||||
import org.lsposed.lspd.impl.utils.LSPosedDexParser;
|
|
||||||
import org.lsposed.lspd.models.Module;
|
import org.lsposed.lspd.models.Module;
|
||||||
import org.lsposed.lspd.nativebridge.HookBridge;
|
import org.lsposed.lspd.nativebridge.HookBridge;
|
||||||
import org.lsposed.lspd.nativebridge.NativeAPI;
|
import org.lsposed.lspd.nativebridge.NativeAPI;
|
||||||
|
|
@ -21,6 +20,8 @@ import org.lsposed.lspd.service.ILSPInjectedModuleService;
|
||||||
import org.lsposed.lspd.util.LspModuleClassLoader;
|
import org.lsposed.lspd.util.LspModuleClassLoader;
|
||||||
import org.lsposed.lspd.util.Utils.Log;
|
import org.lsposed.lspd.util.Utils.Log;
|
||||||
|
|
||||||
|
import org.matrix.vector.impl.utils.VectorDexParser;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
@ -288,7 +289,7 @@ public class LSPosedContext implements XposedInterface {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException {
|
public DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException {
|
||||||
return new LSPosedDexParser(dexData, includeAnnotations);
|
return new VectorDexParser(dexData, includeAnnotations);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
|
|
|
||||||
|
|
@ -1,424 +0,0 @@
|
||||||
package org.lsposed.lspd.impl.utils;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import org.lsposed.lspd.nativebridge.DexParserBridge;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import io.github.libxposed.api.utils.DexParser;
|
|
||||||
|
|
||||||
public class LSPosedDexParser implements DexParser {
|
|
||||||
long cookie;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
final ByteBuffer data;
|
|
||||||
@NonNull
|
|
||||||
final StringId[] strings;
|
|
||||||
@NonNull
|
|
||||||
final TypeId[] typeIds;
|
|
||||||
@NonNull
|
|
||||||
final ProtoId[] protoIds;
|
|
||||||
@NonNull
|
|
||||||
final FieldId[] fieldIds;
|
|
||||||
@NonNull
|
|
||||||
final MethodId[] methodIds;
|
|
||||||
@NonNull
|
|
||||||
final Annotation[] annotations;
|
|
||||||
@NonNull
|
|
||||||
final Array[] arrays;
|
|
||||||
|
|
||||||
public LSPosedDexParser(@NonNull ByteBuffer buffer, boolean includeAnnotations) throws IOException {
|
|
||||||
if (!buffer.isDirect() || !buffer.asReadOnlyBuffer().hasArray()) {
|
|
||||||
data = ByteBuffer.allocateDirect(buffer.capacity());
|
|
||||||
data.put(buffer);
|
|
||||||
} else {
|
|
||||||
data = buffer;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
long[] args = new long[2];
|
|
||||||
args[1] = includeAnnotations ? 1 : 0;
|
|
||||||
var out = (Object[]) DexParserBridge.openDex(buffer, args);
|
|
||||||
cookie = args[0];
|
|
||||||
// out[0]: String[]
|
|
||||||
// out[1]: int[]
|
|
||||||
// out[2]: int[][]
|
|
||||||
// out[3]: int[]
|
|
||||||
// out[4]: int[]
|
|
||||||
// out[5]: int[]
|
|
||||||
// out[6]: Object[]
|
|
||||||
// out[7]: Object[]
|
|
||||||
var strings = (Object[]) out[0];
|
|
||||||
this.strings = new StringId[strings.length];
|
|
||||||
for (int i = 0; i < strings.length; ++i) {
|
|
||||||
this.strings[i] = new LSPosedStringId(i, strings[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeIds = (int[]) out[1];
|
|
||||||
this.typeIds = new TypeId[typeIds.length];
|
|
||||||
for (int i = 0; i < typeIds.length; ++i) {
|
|
||||||
this.typeIds[i] = new LSPosedTypeId(i, typeIds[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var protoIds = (int[][]) out[2];
|
|
||||||
this.protoIds = new ProtoId[protoIds.length];
|
|
||||||
for (int i = 0; i < protoIds.length; ++i) {
|
|
||||||
this.protoIds[i] = new LSPosedProtoId(i, protoIds[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var fieldIds = (int[]) out[3];
|
|
||||||
this.fieldIds = new FieldId[fieldIds.length / 3];
|
|
||||||
for (int i = 0; i < this.fieldIds.length; ++i) {
|
|
||||||
this.fieldIds[i] = new LSPosedFieldId(i, fieldIds[3 * i], fieldIds[3 * i + 1], fieldIds[3 * i + 2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var methodIds = (int[]) out[4];
|
|
||||||
this.methodIds = new MethodId[methodIds.length / 3];
|
|
||||||
for (int i = 0; i < this.methodIds.length / 3; ++i) {
|
|
||||||
this.methodIds[i] = new LSPosedMethodId(i, methodIds[3 * i], methodIds[3 * i + 1], methodIds[3 * i + 2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (out[5] != null && out[6] != null) {
|
|
||||||
var a = (int[]) out[5];
|
|
||||||
var b = (Object[]) out[6];
|
|
||||||
this.annotations = new Annotation[a.length / 2];
|
|
||||||
for (int i = 0; i < this.annotations.length; ++i) {
|
|
||||||
this.annotations[i] = new LSPosedAnnotation(a[2 * i], a[2 * i + 1], (int[]) b[2 * i], (Object[]) b[2 * i + 1]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.annotations = new Annotation[0];
|
|
||||||
}
|
|
||||||
if (out[7] != null) {
|
|
||||||
var b = (Object[]) out[7];
|
|
||||||
this.arrays = new Array[b.length / 2];
|
|
||||||
for (int i = 0; i < this.arrays.length; ++i) {
|
|
||||||
this.arrays[i] = new LSPosedArray((int[]) b[2 * i], (Object[]) b[2 * i + 1]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.arrays = new Array[0];
|
|
||||||
}
|
|
||||||
} catch (Throwable e) {
|
|
||||||
throw new IOException("Invalid dex file", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
synchronized public void close() {
|
|
||||||
if (cookie != 0) {
|
|
||||||
DexParserBridge.closeDex(cookie);
|
|
||||||
cookie = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class LSPosedId<Self extends Id<Self>> implements Id<Self> {
|
|
||||||
final int id;
|
|
||||||
|
|
||||||
LSPosedId(int id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(Self o) {
|
|
||||||
return id - o.getId();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class LSPosedStringId extends LSPosedId<StringId> implements StringId {
|
|
||||||
@NonNull
|
|
||||||
final String string;
|
|
||||||
|
|
||||||
LSPosedStringId(int id, @NonNull Object string) {
|
|
||||||
super(id);
|
|
||||||
this.string = (String) string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public String getString() {
|
|
||||||
return string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LSPosedTypeId extends LSPosedId<TypeId> implements TypeId {
|
|
||||||
@NonNull
|
|
||||||
final StringId descriptor;
|
|
||||||
|
|
||||||
LSPosedTypeId(int id, int descriptor) {
|
|
||||||
super(id);
|
|
||||||
this.descriptor = strings[descriptor];
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public StringId getDescriptor() {
|
|
||||||
return descriptor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LSPosedProtoId extends LSPosedId<ProtoId> implements ProtoId {
|
|
||||||
@NonNull
|
|
||||||
final StringId shorty;
|
|
||||||
@NonNull
|
|
||||||
final TypeId returnType;
|
|
||||||
@Nullable
|
|
||||||
final TypeId[] parameters;
|
|
||||||
|
|
||||||
LSPosedProtoId(int id, @NonNull int[] protoId) {
|
|
||||||
super(id);
|
|
||||||
this.shorty = strings[protoId[0]];
|
|
||||||
this.returnType = typeIds[protoId[1]];
|
|
||||||
if (protoId.length > 2) {
|
|
||||||
this.parameters = new TypeId[protoId.length - 2];
|
|
||||||
for (int i = 2; i < parameters.length; ++i) {
|
|
||||||
this.parameters[i] = typeIds[protoId[i]];
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.parameters = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public StringId getShorty() {
|
|
||||||
return shorty;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public TypeId getReturnType() {
|
|
||||||
return returnType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public TypeId[] getParameters() {
|
|
||||||
return parameters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LSPosedFieldId extends LSPosedId<FieldId> implements FieldId {
|
|
||||||
@NonNull
|
|
||||||
final TypeId type;
|
|
||||||
@NonNull
|
|
||||||
final TypeId declaringClass;
|
|
||||||
@NonNull
|
|
||||||
final StringId name;
|
|
||||||
|
|
||||||
LSPosedFieldId(int id, int type, int declaringClass, int name) {
|
|
||||||
super(id);
|
|
||||||
this.type = typeIds[type];
|
|
||||||
this.declaringClass = typeIds[declaringClass];
|
|
||||||
this.name = strings[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public TypeId getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public TypeId getDeclaringClass() {
|
|
||||||
return declaringClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public StringId getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LSPosedMethodId extends LSPosedId<MethodId> implements MethodId {
|
|
||||||
@NonNull
|
|
||||||
final TypeId declaringClass;
|
|
||||||
@NonNull
|
|
||||||
final ProtoId prototype;
|
|
||||||
@NonNull
|
|
||||||
final StringId name;
|
|
||||||
|
|
||||||
LSPosedMethodId(int id, int declaringClass, int prototype, int name) {
|
|
||||||
super(id);
|
|
||||||
this.declaringClass = typeIds[declaringClass];
|
|
||||||
this.prototype = protoIds[prototype];
|
|
||||||
this.name = strings[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public TypeId getDeclaringClass() {
|
|
||||||
return declaringClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ProtoId getPrototype() {
|
|
||||||
return prototype;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public StringId getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class LSPosedArray implements Array {
|
|
||||||
@NonNull
|
|
||||||
final Value[] values;
|
|
||||||
|
|
||||||
LSPosedArray(int[] elements, @NonNull Object[] values) {
|
|
||||||
this.values = new Value[values.length];
|
|
||||||
for (int i = 0; i < values.length; ++i) {
|
|
||||||
this.values[i] = new LSPosedValue(elements[i], (ByteBuffer) values[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Value[] getValues() {
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LSPosedAnnotation implements Annotation {
|
|
||||||
int visibility;
|
|
||||||
@NonNull
|
|
||||||
final TypeId type;
|
|
||||||
@NonNull
|
|
||||||
final Element[] elements;
|
|
||||||
|
|
||||||
LSPosedAnnotation(int visibility, int type, @NonNull int[] elements, @NonNull Object[] elementValues) {
|
|
||||||
this.visibility = visibility;
|
|
||||||
this.type = typeIds[type];
|
|
||||||
this.elements = new Element[elementValues.length];
|
|
||||||
for (int i = 0; i < elementValues.length; ++i) {
|
|
||||||
this.elements[i] = new LSPosedElement(elements[i * 2], elements[i * 2 + 1], (ByteBuffer) elementValues[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getVisibility() {
|
|
||||||
return visibility;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public TypeId getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Element[] getElements() {
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class LSPosedValue implements Value {
|
|
||||||
final int valueType;
|
|
||||||
@Nullable
|
|
||||||
final byte[] value;
|
|
||||||
|
|
||||||
LSPosedValue(int valueType, @Nullable ByteBuffer value) {
|
|
||||||
this.valueType = valueType;
|
|
||||||
if (value != null) {
|
|
||||||
this.value = new byte[value.remaining()];
|
|
||||||
value.get(this.value);
|
|
||||||
} else {
|
|
||||||
this.value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public byte[] getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getValueType() {
|
|
||||||
return valueType;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LSPosedElement extends LSPosedValue implements Element {
|
|
||||||
@NonNull
|
|
||||||
final StringId name;
|
|
||||||
|
|
||||||
LSPosedElement(int name, int valueType, @Nullable ByteBuffer value) {
|
|
||||||
super(valueType, value);
|
|
||||||
this.name = strings[name];
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public StringId getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public StringId[] getStringId() {
|
|
||||||
return strings;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public TypeId[] getTypeId() {
|
|
||||||
return typeIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public FieldId[] getFieldId() {
|
|
||||||
return fieldIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public MethodId[] getMethodId() {
|
|
||||||
return methodIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ProtoId[] getProtoId() {
|
|
||||||
return protoIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Annotation[] getAnnotations() {
|
|
||||||
return annotations;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public Array[] getArrays() {
|
|
||||||
return arrays;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
synchronized public void visitDefinedClasses(@NonNull ClassVisitor visitor) {
|
|
||||||
if (cookie == 0) {
|
|
||||||
throw new IllegalStateException("Closed");
|
|
||||||
}
|
|
||||||
var classVisitMethod = ClassVisitor.class.getDeclaredMethods()[0];
|
|
||||||
var fieldVisitMethod = FieldVisitor.class.getDeclaredMethods()[0];
|
|
||||||
var methodVisitMethod = MethodVisitor.class.getDeclaredMethods()[0];
|
|
||||||
var methodBodyVisitMethod = MethodBodyVisitor.class.getDeclaredMethods()[0];
|
|
||||||
var stopMethod = EarlyStopVisitor.class.getDeclaredMethods()[0];
|
|
||||||
|
|
||||||
DexParserBridge.visitClass(cookie, visitor, FieldVisitor.class, MethodVisitor.class, classVisitMethod, fieldVisitMethod, methodVisitMethod, methodBodyVisitMethod, stopMethod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
package org.lsposed.lspd.nativebridge;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
|
|
||||||
import dalvik.annotation.optimization.FastNative;
|
|
||||||
import io.github.libxposed.api.utils.DexParser;
|
|
||||||
|
|
||||||
public class DexParserBridge {
|
|
||||||
@FastNative
|
|
||||||
public static native Object openDex(ByteBuffer data, long[] args) throws IOException;
|
|
||||||
|
|
||||||
@FastNative
|
|
||||||
public static native void closeDex(long cookie);
|
|
||||||
|
|
||||||
@FastNative
|
|
||||||
public static native void visitClass(long cookie, Object visitor, Class<DexParser.FieldVisitor> fieldVisitorClass, Class<DexParser.MethodVisitor> methodVisitorClass, Method classVisitMethod, Method fieldVisitMethod, Method methodVisitMethod, Method methodBodyVisitMethod, Method stopMethod);
|
|
||||||
}
|
|
||||||
|
|
@ -6,6 +6,7 @@ appcenter = "5.0.5"
|
||||||
libxposed = "100"
|
libxposed = "100"
|
||||||
glide = "5.0.5"
|
glide = "5.0.5"
|
||||||
okhttp = "5.3.2"
|
okhttp = "5.3.2"
|
||||||
|
ktfmt = "0.25.0"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
agp-lib = { id = "com.android.library", version.ref = "agp" }
|
agp-lib = { id = "com.android.library", version.ref = "agp" }
|
||||||
|
|
@ -13,6 +14,7 @@ agp-app = { id = "com.android.application", version.ref = "agp" }
|
||||||
kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
nav-safeargs = { id = "androidx.navigation.safeargs", version.ref = "nav" }
|
nav-safeargs = { id = "androidx.navigation.safeargs", version.ref = "nav" }
|
||||||
autoresconfig = { id = "dev.rikka.tools.autoresconfig", version = "1.2.2" }
|
autoresconfig = { id = "dev.rikka.tools.autoresconfig", version = "1.2.2" }
|
||||||
|
ktfmt = { id = "com.ncorti.ktfmt.gradle", version.ref = "ktfmt" }
|
||||||
materialthemebuilder = { id = "dev.rikka.tools.materialthemebuilder", version = "1.5.1" }
|
materialthemebuilder = { id = "dev.rikka.tools.materialthemebuilder", version = "1.5.1" }
|
||||||
lsplugin-resopt = { id = "org.lsposed.lsplugin.resopt", version = "1.6" }
|
lsplugin-resopt = { id = "org.lsposed.lsplugin.resopt", version = "1.6" }
|
||||||
lsplugin-apksign = { id = "org.lsposed.lsplugin.apksign", version = "1.4" }
|
lsplugin-apksign = { id = "org.lsposed.lsplugin.apksign", version = "1.4" }
|
||||||
|
|
|
||||||
|
|
@ -37,4 +37,5 @@ include(
|
||||||
":magisk-loader",
|
":magisk-loader",
|
||||||
":services:manager-service",
|
":services:manager-service",
|
||||||
":services:daemon-service",
|
":services:daemon-service",
|
||||||
|
":xposed",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
# Xposed API implementation of the Vector framework
|
||||||
|
|
||||||
|
LSPosed is being refactored into a new project `Vector`.
|
||||||
|
|
||||||
|
This sub-project `xposed`, written in Kotlin, will be refactored from the `core` sub-project written in `Java`.
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.agp.lib)
|
||||||
|
alias(libs.plugins.kotlin)
|
||||||
|
alias(libs.plugins.ktfmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
ktfmt { kotlinLangStyle() }
|
||||||
|
|
||||||
|
val versionCodeProvider: Provider<String> by rootProject.extra
|
||||||
|
val versionNameProvider: Provider<String> by rootProject.extra
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "org.matrix.vector.xposed"
|
||||||
|
|
||||||
|
buildFeatures { androidResources { enable = false } }
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(libs.libxposed.api)
|
||||||
|
compileOnly(libs.androidx.annotation)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,316 @@
|
||||||
|
package org.matrix.vector.impl.utils
|
||||||
|
|
||||||
|
import io.github.libxposed.api.utils.DexParser
|
||||||
|
import io.github.libxposed.api.utils.DexParser.*
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import org.lsposed.lspd.nativebridge.DexParserBridge
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Kotlin implementation of [DexParser] for Vector.
|
||||||
|
*
|
||||||
|
* This class acts as a high-level wrapper around the native C++ DexParser. It maps raw JNI data
|
||||||
|
* structures (integer arrays, flat buffers) into usable object graphs (StringId, TypeId, MethodId,
|
||||||
|
* etc.).
|
||||||
|
*/
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
class VectorDexParser(buffer: ByteBuffer, includeAnnotations: Boolean) : DexParser {
|
||||||
|
|
||||||
|
private var cookie: Long = 0
|
||||||
|
private val data: ByteBuffer
|
||||||
|
|
||||||
|
// Internal storage for parsed DEX structures.
|
||||||
|
// We use private properties and explicit getter methods as requested.
|
||||||
|
private val internalStrings: Array<StringId>
|
||||||
|
private val internalTypeIds: Array<TypeId>
|
||||||
|
private val internalProtoIds: Array<ProtoId>
|
||||||
|
private val internalFieldIds: Array<FieldId>
|
||||||
|
private val internalMethodIds: Array<MethodId>
|
||||||
|
private val internalAnnotations: Array<DexParser.Annotation>
|
||||||
|
private val internalArrays: Array<DexParser.Array>
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Ensure the buffer is Direct and accessible by native code
|
||||||
|
data =
|
||||||
|
if (!buffer.isDirect || !buffer.asReadOnlyBuffer().hasArray()) {
|
||||||
|
ByteBuffer.allocateDirect(buffer.capacity()).apply {
|
||||||
|
put(buffer)
|
||||||
|
// Ensure position is reset for reading if needed,
|
||||||
|
// though native uses address
|
||||||
|
flip()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val args = LongArray(2)
|
||||||
|
args[1] = if (includeAnnotations) 1 else 0
|
||||||
|
|
||||||
|
// Call Native Bridge
|
||||||
|
// Returns a raw Object[] containing headers and pools
|
||||||
|
val out = DexParserBridge.openDex(data, args) as Array<Any?>
|
||||||
|
cookie = args[0]
|
||||||
|
|
||||||
|
// --- Parse Strings (Index 0) ---
|
||||||
|
val rawStrings = out[0] as Array<String>
|
||||||
|
internalStrings = Array(rawStrings.size) { i -> VectorStringId(i, rawStrings[i]) }
|
||||||
|
|
||||||
|
// --- Parse Type IDs (Index 1) ---
|
||||||
|
val rawTypeIds = out[1] as IntArray
|
||||||
|
internalTypeIds = Array(rawTypeIds.size) { i -> VectorTypeId(i, rawTypeIds[i]) }
|
||||||
|
|
||||||
|
// --- Parse Proto IDs (Index 2) ---
|
||||||
|
val rawProtoIds = out[2] as Array<IntArray>
|
||||||
|
internalProtoIds = Array(rawProtoIds.size) { i -> VectorProtoId(i, rawProtoIds[i]) }
|
||||||
|
|
||||||
|
// --- Parse Field IDs (Index 3) ---
|
||||||
|
val rawFieldIds = out[3] as IntArray
|
||||||
|
// Each field is represented by 3 integers (class_idx, type_idx, name_idx)
|
||||||
|
internalFieldIds =
|
||||||
|
Array(rawFieldIds.size / 3) { i ->
|
||||||
|
VectorFieldId(
|
||||||
|
i,
|
||||||
|
rawFieldIds[3 * i],
|
||||||
|
rawFieldIds[3 * i + 1],
|
||||||
|
rawFieldIds[3 * i + 2],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Parse Method IDs (Index 4) ---
|
||||||
|
val rawMethodIds = out[4] as IntArray
|
||||||
|
// Each method is represented by 3 integers (class_idx, proto_idx, name_idx)
|
||||||
|
internalMethodIds =
|
||||||
|
Array(rawMethodIds.size / 3) { i ->
|
||||||
|
VectorMethodId(
|
||||||
|
i,
|
||||||
|
rawMethodIds[3 * i],
|
||||||
|
rawMethodIds[3 * i + 1],
|
||||||
|
rawMethodIds[3 * i + 2],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Parse Annotations (Index 5 & 6) ---
|
||||||
|
val rawAnnotationMetadata = out[5] as? IntArray
|
||||||
|
val rawAnnotationValues = out[6] as? Array<Any?>
|
||||||
|
|
||||||
|
internalAnnotations =
|
||||||
|
if (rawAnnotationMetadata != null && rawAnnotationValues != null) {
|
||||||
|
Array(rawAnnotationMetadata.size / 2) { i ->
|
||||||
|
// Metadata: [visibility, type_idx]
|
||||||
|
// Values: [name_indices[], values[]]
|
||||||
|
val elementsMeta = rawAnnotationValues[2 * i] as IntArray
|
||||||
|
val elementsData = rawAnnotationValues[2 * i + 1] as Array<Any?>
|
||||||
|
VectorAnnotation(
|
||||||
|
rawAnnotationMetadata[2 * i],
|
||||||
|
rawAnnotationMetadata[2 * i + 1],
|
||||||
|
elementsMeta,
|
||||||
|
elementsData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Parse Arrays (Index 7) ---
|
||||||
|
val rawArrays = out[7] as? Array<Any?>
|
||||||
|
internalArrays =
|
||||||
|
if (rawArrays != null) {
|
||||||
|
Array(rawArrays.size / 2) { i ->
|
||||||
|
val types = rawArrays[2 * i] as IntArray
|
||||||
|
val values = rawArrays[2 * i + 1] as Array<Any?>
|
||||||
|
VectorArray(types, values)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
emptyArray()
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
throw IOException("Invalid dex file", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun close() {
|
||||||
|
if (cookie != 0L) {
|
||||||
|
DexParserBridge.closeDex(cookie)
|
||||||
|
cookie = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getStringId(): Array<StringId> = internalStrings
|
||||||
|
|
||||||
|
override fun getTypeId(): Array<TypeId> = internalTypeIds
|
||||||
|
|
||||||
|
override fun getFieldId(): Array<FieldId> = internalFieldIds
|
||||||
|
|
||||||
|
override fun getMethodId(): Array<MethodId> = internalMethodIds
|
||||||
|
|
||||||
|
override fun getProtoId(): Array<ProtoId> = internalProtoIds
|
||||||
|
|
||||||
|
override fun getAnnotations(): Array<DexParser.Annotation> = internalAnnotations
|
||||||
|
|
||||||
|
override fun getArrays(): Array<DexParser.Array> = internalArrays
|
||||||
|
|
||||||
|
override fun visitDefinedClasses(visitor: ClassVisitor) {
|
||||||
|
if (cookie == 0L) {
|
||||||
|
throw IllegalStateException("Closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accessing [0] is fragile
|
||||||
|
val classVisitMethod = ClassVisitor::class.java.declaredMethods[0]
|
||||||
|
val fieldVisitMethod = FieldVisitor::class.java.declaredMethods[0]
|
||||||
|
val methodVisitMethod = MethodVisitor::class.java.declaredMethods[0]
|
||||||
|
val methodBodyVisitMethod = MethodBodyVisitor::class.java.declaredMethods[0]
|
||||||
|
val stopMethod = EarlyStopVisitor::class.java.declaredMethods[0]
|
||||||
|
|
||||||
|
DexParserBridge.visitClass(
|
||||||
|
cookie,
|
||||||
|
visitor,
|
||||||
|
FieldVisitor::class.java,
|
||||||
|
MethodVisitor::class.java,
|
||||||
|
classVisitMethod,
|
||||||
|
fieldVisitMethod,
|
||||||
|
methodVisitMethod,
|
||||||
|
methodBodyVisitMethod,
|
||||||
|
stopMethod,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Base implementation for all Dex IDs. */
|
||||||
|
private open class VectorId<Self : Id<Self>>(private val id: Int) : Id<Self> {
|
||||||
|
override fun getId(): Int = id
|
||||||
|
|
||||||
|
override fun compareTo(other: Self): Int = id - other.id
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class VectorStringId(id: Int, private val string: String) :
|
||||||
|
VectorId<StringId>(id), StringId {
|
||||||
|
override fun getString(): String = string
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class VectorTypeId(id: Int, descriptorIdx: Int) : VectorId<TypeId>(id), TypeId {
|
||||||
|
private val descriptor: StringId = internalStrings[descriptorIdx]
|
||||||
|
|
||||||
|
override fun getDescriptor(): StringId = descriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class VectorProtoId(id: Int, protoData: IntArray) :
|
||||||
|
VectorId<ProtoId>(id), ProtoId {
|
||||||
|
|
||||||
|
private val shorty: StringId = internalStrings[protoData[0]]
|
||||||
|
private val returnType: TypeId = internalTypeIds[protoData[1]]
|
||||||
|
private val parameters: Array<TypeId>?
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (protoData.size > 2) {
|
||||||
|
// protoData format: [shorty_idx, return_type_idx, param1_idx, param2_idx...]
|
||||||
|
parameters = Array(protoData.size - 2) { i -> internalTypeIds[protoData[i + 2]] }
|
||||||
|
} else {
|
||||||
|
parameters = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getShorty(): StringId = shorty
|
||||||
|
|
||||||
|
override fun getReturnType(): TypeId = returnType
|
||||||
|
|
||||||
|
override fun getParameters(): Array<TypeId>? = parameters
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class VectorFieldId(id: Int, classIdx: Int, typeIdx: Int, nameIdx: Int) :
|
||||||
|
VectorId<FieldId>(id), FieldId {
|
||||||
|
|
||||||
|
private val declaringClass: TypeId = internalTypeIds[classIdx]
|
||||||
|
private val type: TypeId = internalTypeIds[typeIdx]
|
||||||
|
private val name: StringId = internalStrings[nameIdx]
|
||||||
|
|
||||||
|
override fun getType(): TypeId = type
|
||||||
|
|
||||||
|
override fun getDeclaringClass(): TypeId = declaringClass
|
||||||
|
|
||||||
|
override fun getName(): StringId = name
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class VectorMethodId(id: Int, classIdx: Int, protoIdx: Int, nameIdx: Int) :
|
||||||
|
VectorId<MethodId>(id), MethodId {
|
||||||
|
|
||||||
|
private val declaringClass: TypeId = internalTypeIds[classIdx]
|
||||||
|
private val prototype: ProtoId = internalProtoIds[protoIdx]
|
||||||
|
private val name: StringId = internalStrings[nameIdx]
|
||||||
|
|
||||||
|
override fun getDeclaringClass(): TypeId = declaringClass
|
||||||
|
|
||||||
|
override fun getPrototype(): ProtoId = prototype
|
||||||
|
|
||||||
|
override fun getName(): StringId = name
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VectorArray(elementsTypes: IntArray, valuesData: Array<Any?>) : DexParser.Array {
|
||||||
|
|
||||||
|
private val values: Array<Value>
|
||||||
|
|
||||||
|
init {
|
||||||
|
values =
|
||||||
|
Array(valuesData.size) { i ->
|
||||||
|
VectorValue(elementsTypes[i], valuesData[i] as? ByteBuffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getValues(): Array<Value> = values
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class VectorAnnotation(
|
||||||
|
private val visibility: Int,
|
||||||
|
typeIdx: Int,
|
||||||
|
elementNameIndices: IntArray,
|
||||||
|
elementValues: Array<Any?>,
|
||||||
|
) : DexParser.Annotation {
|
||||||
|
|
||||||
|
private val type: TypeId = internalTypeIds[typeIdx]
|
||||||
|
private val elements: Array<Element>
|
||||||
|
|
||||||
|
init {
|
||||||
|
elements =
|
||||||
|
Array(elementValues.size) { i ->
|
||||||
|
// Flattened structure from JNI: names are at 2*i, types at 2*i+1
|
||||||
|
VectorElement(
|
||||||
|
elementNameIndices[i * 2],
|
||||||
|
elementNameIndices[i * 2 + 1], // valueType
|
||||||
|
elementValues[i] as? ByteBuffer,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getVisibility(): Int = visibility
|
||||||
|
|
||||||
|
override fun getType(): TypeId = type
|
||||||
|
|
||||||
|
override fun getElements(): Array<Element> = elements
|
||||||
|
}
|
||||||
|
|
||||||
|
private open class VectorValue(private val valueType: Int, buffer: ByteBuffer?) : Value {
|
||||||
|
|
||||||
|
private val value: ByteArray?
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (buffer != null) {
|
||||||
|
value = ByteArray(buffer.remaining())
|
||||||
|
buffer.get(value)
|
||||||
|
} else {
|
||||||
|
value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getValue(): ByteArray? = value
|
||||||
|
|
||||||
|
override fun getValueType(): Int = valueType
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class VectorElement(nameIdx: Int, valueType: Int, value: ByteBuffer?) :
|
||||||
|
VectorValue(valueType, value), Element {
|
||||||
|
|
||||||
|
private val name: StringId = internalStrings[nameIdx]
|
||||||
|
|
||||||
|
override fun getName(): StringId = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.lsposed.lspd.nativebridge
|
||||||
|
// TODO: refactor the JNI and thus change the package name
|
||||||
|
|
||||||
|
import dalvik.annotation.optimization.FastNative
|
||||||
|
import io.github.libxposed.api.utils.DexParser
|
||||||
|
import java.io.IOException
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
object DexParserBridge {
|
||||||
|
@JvmStatic
|
||||||
|
@FastNative
|
||||||
|
@Throws(IOException::class)
|
||||||
|
external fun openDex(data: ByteBuffer, args: LongArray): Any
|
||||||
|
|
||||||
|
@JvmStatic @FastNative external fun closeDex(cookie: Long)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@FastNative
|
||||||
|
external fun visitClass(
|
||||||
|
cookie: Long,
|
||||||
|
visitor: Any,
|
||||||
|
fieldVisitorClass: Class<DexParser.FieldVisitor>,
|
||||||
|
methodVisitorClass: Class<DexParser.MethodVisitor>,
|
||||||
|
classVisitMethod: Method,
|
||||||
|
fieldVisitMethod: Method,
|
||||||
|
methodVisitMethod: Method,
|
||||||
|
methodBodyVisitMethod: Method,
|
||||||
|
stopMethod: Method,
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue