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 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 // 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 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 } }