From 1641c7e385b62ce3d742f9afa70ba94b8096eee7 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Mon, 19 Jan 2026 10:26:53 +0100 Subject: [PATCH 1/2] Fix and refactor DexParser implementation 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. --- build.gradle.kts | 11 + core/build.gradle.kts | 1 + .../org/lsposed/lspd/impl/LSPosedContext.java | 5 +- .../lspd/impl/utils/LSPosedDexParser.java | 424 ------------------ .../lspd/nativebridge/DexParserBridge.java | 19 - gradle/libs.versions.toml | 2 + settings.gradle.kts | 1 + xposed/README.md | 5 + xposed/build.gradle.kts | 21 + .../vector/impl/utils/VectorDexParser.kt | 317 +++++++++++++ .../vector/nativebridge/DexParserBridge.kt | 30 ++ 11 files changed, 391 insertions(+), 445 deletions(-) delete mode 100644 core/src/main/java/org/lsposed/lspd/impl/utils/LSPosedDexParser.java delete mode 100644 core/src/main/java/org/lsposed/lspd/nativebridge/DexParserBridge.java create mode 100644 xposed/README.md create mode 100644 xposed/build.gradle.kts create mode 100644 xposed/src/main/kotlin/org/matrix/vector/impl/utils/VectorDexParser.kt create mode 100644 xposed/src/main/kotlin/org/matrix/vector/nativebridge/DexParserBridge.kt diff --git a/build.gradle.kts b/build.gradle.kts index cda316668..50028f667 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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("format") { + source = project.fileTree(rootDir) + include("*.gradle.kts", "*/build.gradle.kts") + dependsOn(":xposed:ktfmtFormat") +} + +ktfmt { kotlinLangStyle() } diff --git a/core/build.gradle.kts b/core/build.gradle.kts index baccd0e0b..75f2c421f 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -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) } diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java index 9c6b19634..3fde28020 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java @@ -13,7 +13,6 @@ 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.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 void log(@NonNull String message, @NonNull Throwable throwable) { @Override public DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException { - return new LSPosedDexParser(dexData, includeAnnotations); + return new VectorDexParser(dexData, includeAnnotations); } @NonNull diff --git a/core/src/main/java/org/lsposed/lspd/impl/utils/LSPosedDexParser.java b/core/src/main/java/org/lsposed/lspd/impl/utils/LSPosedDexParser.java deleted file mode 100644 index 2c4b5e5fc..000000000 --- a/core/src/main/java/org/lsposed/lspd/impl/utils/LSPosedDexParser.java +++ /dev/null @@ -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> implements Id { - 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 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 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 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 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 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); - } -} diff --git a/core/src/main/java/org/lsposed/lspd/nativebridge/DexParserBridge.java b/core/src/main/java/org/lsposed/lspd/nativebridge/DexParserBridge.java deleted file mode 100644 index 498834794..000000000 --- a/core/src/main/java/org/lsposed/lspd/nativebridge/DexParserBridge.java +++ /dev/null @@ -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 fieldVisitorClass, Class methodVisitorClass, Method classVisitMethod, Method fieldVisitMethod, Method methodVisitMethod, Method methodBodyVisitMethod, Method stopMethod); -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 97c1f7fc0..f47205974 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 2d4e03d10..6d05f2544 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -37,4 +37,5 @@ include( ":magisk-loader", ":services:manager-service", ":services:daemon-service", + ":xposed", ) diff --git a/xposed/README.md b/xposed/README.md new file mode 100644 index 000000000..bbc76abaf --- /dev/null +++ b/xposed/README.md @@ -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`. diff --git a/xposed/build.gradle.kts b/xposed/build.gradle.kts new file mode 100644 index 000000000..099944bcc --- /dev/null +++ b/xposed/build.gradle.kts @@ -0,0 +1,21 @@ +plugins { + alias(libs.plugins.agp.lib) + alias(libs.plugins.kotlin) + alias(libs.plugins.ktfmt) +} + +ktfmt { kotlinLangStyle() } + +val versionCodeProvider: Provider by rootProject.extra +val versionNameProvider: Provider by rootProject.extra + +android { + namespace = "org.matrix.vector.xposed" + + buildFeatures { androidResources { enable = false } } +} + +dependencies { + api(libs.libxposed.api) + compileOnly(libs.androidx.annotation) +} diff --git a/xposed/src/main/kotlin/org/matrix/vector/impl/utils/VectorDexParser.kt b/xposed/src/main/kotlin/org/matrix/vector/impl/utils/VectorDexParser.kt new file mode 100644 index 000000000..5c505ad98 --- /dev/null +++ b/xposed/src/main/kotlin/org/matrix/vector/impl/utils/VectorDexParser.kt @@ -0,0 +1,317 @@ +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.matrix.vector.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 + private val internalTypeIds: Array + private val internalProtoIds: Array + private val internalFieldIds: Array + private val internalMethodIds: Array + private val internalAnnotations: Array + private val internalArrays: 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 + cookie = args[0] + + // --- Parse Strings (Index 0) --- + val rawStrings = out[0] as Array + 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 + 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 + // FIX for Bug A: The original code iterated up to (length / 3) / 3. + // Here we correctly allocate size N/3 and iterate 0..size-1. + 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 + + 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 + VectorAnnotation( + rawAnnotationMetadata[2 * i], + rawAnnotationMetadata[2 * i + 1], + elementsMeta, + elementsData, + ) + } + } else { + emptyArray() + } + + // --- Parse Arrays (Index 7) --- + val rawArrays = out[7] as? Array + internalArrays = + if (rawArrays != null) { + Array(rawArrays.size / 2) { i -> + val types = rawArrays[2 * i] as IntArray + val values = rawArrays[2 * i + 1] as Array + 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 = internalStrings + + override fun getTypeId(): Array = internalTypeIds + + override fun getFieldId(): Array = internalFieldIds + + override fun getMethodId(): Array = internalMethodIds + + override fun getProtoId(): Array = internalProtoIds + + override fun getAnnotations(): Array = internalAnnotations + + override fun getArrays(): 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>(private val id: Int) : Id { + 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(id), StringId { + override fun getString(): String = string + } + + private inner class VectorTypeId(id: Int, descriptorIdx: Int) : VectorId(id), TypeId { + private val descriptor: StringId = internalStrings[descriptorIdx] + + override fun getDescriptor(): StringId = descriptor + } + + private inner class VectorProtoId(id: Int, protoData: IntArray) : + VectorId(id), ProtoId { + + private val shorty: StringId = internalStrings[protoData[0]] + private val returnType: TypeId = internalTypeIds[protoData[1]] + private val parameters: Array? + + 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? = parameters + } + + private inner class VectorFieldId(id: Int, classIdx: Int, typeIdx: Int, nameIdx: Int) : + VectorId(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(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) : DexParser.Array { + + private val values: Array + + init { + values = + Array(valuesData.size) { i -> + VectorValue(elementsTypes[i], valuesData[i] as? ByteBuffer) + } + } + + override fun getValues(): Array = values + } + + private inner class VectorAnnotation( + private val visibility: Int, + typeIdx: Int, + elementNameIndices: IntArray, + elementValues: Array, + ) : DexParser.Annotation { + + private val type: TypeId = internalTypeIds[typeIdx] + private val elements: Array + + 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 = 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 + } +} diff --git a/xposed/src/main/kotlin/org/matrix/vector/nativebridge/DexParserBridge.kt b/xposed/src/main/kotlin/org/matrix/vector/nativebridge/DexParserBridge.kt new file mode 100644 index 000000000..35d46ea5b --- /dev/null +++ b/xposed/src/main/kotlin/org/matrix/vector/nativebridge/DexParserBridge.kt @@ -0,0 +1,30 @@ +package org.matrix.vector.nativebridge + +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, + methodVisitorClass: Class, + classVisitMethod: Method, + fieldVisitMethod: Method, + methodVisitMethod: Method, + methodBodyVisitMethod: Method, + stopMethod: Method, + ) +} From 03d757b344f60d5635b80bc9b12b6e59acb98a8c Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Mon, 19 Jan 2026 15:57:32 +0100 Subject: [PATCH 2/2] Correct the JNI package name --- .../kotlin/org/matrix/vector/impl/utils/VectorDexParser.kt | 2 +- .../kotlin/org/matrix/vector/nativebridge/DexParserBridge.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/xposed/src/main/kotlin/org/matrix/vector/impl/utils/VectorDexParser.kt b/xposed/src/main/kotlin/org/matrix/vector/impl/utils/VectorDexParser.kt index 5c505ad98..8d2f45f66 100644 --- a/xposed/src/main/kotlin/org/matrix/vector/impl/utils/VectorDexParser.kt +++ b/xposed/src/main/kotlin/org/matrix/vector/impl/utils/VectorDexParser.kt @@ -4,7 +4,7 @@ import io.github.libxposed.api.utils.DexParser import io.github.libxposed.api.utils.DexParser.* import java.io.IOException import java.nio.ByteBuffer -import org.matrix.vector.nativebridge.DexParserBridge +import org.lsposed.lspd.nativebridge.DexParserBridge /** * Kotlin implementation of [DexParser] for Vector. diff --git a/xposed/src/main/kotlin/org/matrix/vector/nativebridge/DexParserBridge.kt b/xposed/src/main/kotlin/org/matrix/vector/nativebridge/DexParserBridge.kt index 35d46ea5b..17bda9e76 100644 --- a/xposed/src/main/kotlin/org/matrix/vector/nativebridge/DexParserBridge.kt +++ b/xposed/src/main/kotlin/org/matrix/vector/nativebridge/DexParserBridge.kt @@ -1,4 +1,5 @@ -package org.matrix.vector.nativebridge +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