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.CommonExtension
|
||||
import com.android.build.gradle.api.AndroidBasePlugin
|
||||
import com.ncorti.ktfmt.gradle.tasks.KtfmtFormatTask
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.lsplugin.cmaker)
|
||||
|
|
@ -27,6 +28,8 @@ plugins {
|
|||
alias(libs.plugins.agp.lib) apply false
|
||||
alias(libs.plugins.agp.app) apply false
|
||||
alias(libs.plugins.nav.safeargs) apply false
|
||||
alias(libs.plugins.kotlin) apply false
|
||||
alias(libs.plugins.ktfmt)
|
||||
}
|
||||
|
||||
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.services.daemonService)
|
||||
implementation(projects.services.managerService)
|
||||
implementation(projects.xposed)
|
||||
compileOnly(libs.androidx.annotation)
|
||||
compileOnly(projects.hiddenapi.stubs)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.lsposed.lspd.core.BuildConfig;
|
||||
import org.lsposed.lspd.impl.utils.LSPosedDexParser;
|
||||
import org.lsposed.lspd.models.Module;
|
||||
import org.lsposed.lspd.nativebridge.HookBridge;
|
||||
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.Utils.Log;
|
||||
|
||||
import org.matrix.vector.impl.utils.VectorDexParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
|
@ -288,7 +289,7 @@ public class LSPosedContext implements XposedInterface {
|
|||
|
||||
@Override
|
||||
public DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException {
|
||||
return new LSPosedDexParser(dexData, includeAnnotations);
|
||||
return new VectorDexParser(dexData, includeAnnotations);
|
||||
}
|
||||
|
||||
@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"
|
||||
glide = "5.0.5"
|
||||
okhttp = "5.3.2"
|
||||
ktfmt = "0.25.0"
|
||||
|
||||
[plugins]
|
||||
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" }
|
||||
nav-safeargs = { id = "androidx.navigation.safeargs", version.ref = "nav" }
|
||||
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" }
|
||||
lsplugin-resopt = { id = "org.lsposed.lsplugin.resopt", version = "1.6" }
|
||||
lsplugin-apksign = { id = "org.lsposed.lsplugin.apksign", version = "1.4" }
|
||||
|
|
|
|||
|
|
@ -37,4 +37,5 @@ include(
|
|||
":magisk-loader",
|
||||
":services:manager-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