Skip to content

Commit

Permalink
feat: migrate to dexlib
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Removed usage of ASM library
  • Loading branch information
oSumAtrIX committed Jun 5, 2022
1 parent 6299b9e commit 3651981
Show file tree
Hide file tree
Showing 25 changed files with 591 additions and 542 deletions.
5 changes: 1 addition & 4 deletions build.gradle.kts
Expand Up @@ -12,10 +12,7 @@ repositories {

dependencies {
implementation(kotlin("stdlib"))
implementation("org.ow2.asm:asm:9.2")
implementation("org.ow2.asm:asm-util:9.2")
implementation("org.ow2.asm:asm-tree:9.2")
implementation("org.ow2.asm:asm-commons:9.2")
implementation("com.github.lanchon.dexpatcher:multidexlib2:2.3.4")
implementation("io.github.microutils:kotlin-logging:2.1.21")
testImplementation("ch.qos.logback:logback-classic:1.2.11") // use your own logger!
testImplementation(kotlin("test"))
Expand Down
74 changes: 38 additions & 36 deletions src/main/kotlin/app/revanced/patcher/Patcher.kt
Expand Up @@ -3,49 +3,51 @@ package app.revanced.patcher
import app.revanced.patcher.cache.Cache
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.resolver.MethodResolver
import app.revanced.patcher.signature.Signature
import app.revanced.patcher.util.Io
import org.objectweb.asm.tree.ClassNode
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import app.revanced.patcher.signature.MethodSignature
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.MultiDexIO
import org.jf.dexlib2.Opcodes
import org.jf.dexlib2.iface.ClassDef
import org.jf.dexlib2.iface.DexFile
import java.io.File

/**
* The Patcher class.
* ***It is of utmost importance that the input and output streams are NEVER closed.***
*
* @param input the input stream to read from, must be a JAR
* @param output the output stream to write to
* @param signatures the signatures
* @sample app.revanced.patcher.PatcherTest
* @throws IOException if one of the streams are closed
*/
class Patcher(
private val input: InputStream,
private val output: OutputStream,
signatures: Array<Signature>,
) {
var cache: Cache
input: File,
private val output: File,
signatures: Array<MethodSignature>,

private var io: Io
private val patches = mutableListOf<Patch>()
) {
private val cache: Cache
private val patches = mutableSetOf<Patch>()

init {
val classes = mutableListOf<ClassNode>()
io = Io(input, output, classes)
io.readFromJar()
cache = Cache(classes, MethodResolver(classes, signatures).resolve())
// TODO: find a way to load all dex classes, the code below only loads the first .dex file
val dexFile = MultiDexIO.readDexFile(true, input, BasicDexFileNamer(), Opcodes.getDefault(), null)
cache = Cache(dexFile.classes, MethodResolver(dexFile.classes, signatures).resolve())
}

/**
* Saves the output to the output stream.
* Calling this method will close the input and output streams,
* meaning this method should NEVER be called after.
*
* @throws IOException if one of the streams are closed
*/
fun save() {
io.saveAsJar()
val newDexFile = object : DexFile {
override fun getClasses(): MutableSet<out ClassDef> {
// TODO: find a way to return a set with a custom iterator
// TODO: the iterator would return the proxied class matching the current index of the list
// TODO: instead of the original class
for (classProxy in cache.classProxy) {
if (!classProxy.proxyused) continue
// TODO: merge this class with cache.classes somehow in an iterator
classProxy.mutatedClass
}
return cache.classes.toMutableSet()
}

override fun getOpcodes(): Opcodes {
// TODO find a way to get the opcodes format
return Opcodes.getDefault()
}
}

// TODO: not sure about maxDexPoolSize & we should use the multithreading overload for writeDexFile
MultiDexIO.writeDexFile(true, output, BasicDexFileNamer(), newDexFile, 10, null)
}

fun addPatches(vararg patches: Patch) {
Expand All @@ -67,4 +69,4 @@ class Patcher(
}
}
}
}
}
28 changes: 22 additions & 6 deletions src/main/kotlin/app/revanced/patcher/cache/Cache.kt
@@ -1,14 +1,30 @@
package app.revanced.patcher.cache

import org.objectweb.asm.tree.ClassNode
import app.revanced.patcher.cache.proxy.ClassProxy
import app.revanced.patcher.signature.MethodSignatureScanResult
import org.jf.dexlib2.iface.ClassDef

class Cache(
val classes: List<ClassNode>,
val methods: MethodMap
)
internal val classes: Set<ClassDef>,
val resolvedMethods: MethodMap
) {
internal val classProxy = mutableListOf<ClassProxy>()

class MethodMap : LinkedHashMap<String, PatchData>() {
override fun get(key: String): PatchData {
fun findClass(predicate: (ClassDef) -> Boolean): ClassProxy? {
// if a class has been found with the given predicate,
val foundClass = classes.singleOrNull(predicate) ?: return null
// create a class proxy with the index of the class in the classes list
// TODO: There might be a more elegant way to the comment above
val classProxy = ClassProxy(foundClass, classes.indexOf(foundClass))
// add it to the cache and
this.classProxy.add(classProxy)
// return the proxy class
return classProxy
}
}

class MethodMap : LinkedHashMap<String, MethodSignatureScanResult>() {
override fun get(key: String): MethodSignatureScanResult {
return super.get(key) ?: throw MethodNotFoundException("Method $key was not found in the method cache")
}
}
Expand Down
22 changes: 0 additions & 22 deletions src/main/kotlin/app/revanced/patcher/cache/PatchData.kt

This file was deleted.

21 changes: 21 additions & 0 deletions src/main/kotlin/app/revanced/patcher/cache/proxy/ClassProxy.kt
@@ -0,0 +1,21 @@
package app.revanced.patcher.cache.proxy

import app.revanced.patcher.cache.proxy.mutableTypes.MutableClass
import org.jf.dexlib2.iface.ClassDef


class ClassProxy(
val immutableClass: ClassDef,
val originalClassIndex: Int,
) {
internal var proxyused = false
internal lateinit var mutatedClass: MutableClass

fun resolve(): MutableClass {
if (!proxyused) {
proxyused = true
mutatedClass = MutableClass(immutableClass)
}
return mutatedClass
}
}
@@ -0,0 +1,29 @@
package app.revanced.patcher.cache.proxy.mutableTypes

import app.revanced.patcher.cache.proxy.mutableTypes.MutableAnnotationElement.Companion.toMutable
import org.jf.dexlib2.base.BaseAnnotation
import org.jf.dexlib2.iface.Annotation

class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
private val visibility = annotation.visibility
private val type = annotation.type
private val elements = annotation.elements.map { element -> element.toMutable() }.toMutableSet()

override fun getType(): String {
return type
}

override fun getElements(): MutableSet<MutableAnnotationElement> {
return elements
}

override fun getVisibility(): Int {
return visibility
}

companion object {
fun Annotation.toMutable(): MutableAnnotation {
return MutableAnnotation(this)
}
}
}
@@ -0,0 +1,33 @@
package app.revanced.patcher.cache.proxy.mutableTypes

import app.revanced.patcher.cache.proxy.mutableTypes.MutableEncodedValue.Companion.toMutable
import org.jf.dexlib2.base.BaseAnnotationElement
import org.jf.dexlib2.iface.AnnotationElement
import org.jf.dexlib2.iface.value.EncodedValue

class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() {
private var name = annotationElement.name
private var value = annotationElement.value.toMutable()

fun setName(name: String) {
this.name = name
}

fun setValue(value: MutableEncodedValue) {
this.value = value
}

override fun getName(): String {
return name
}

override fun getValue(): EncodedValue {
return value
}

companion object {
fun AnnotationElement.toMutable(): MutableAnnotationElement {
return MutableAnnotationElement(this)
}
}
}
@@ -0,0 +1,94 @@
package app.revanced.patcher.cache.proxy.mutableTypes

import app.revanced.patcher.cache.proxy.mutableTypes.MutableAnnotation.Companion.toMutable
import app.revanced.patcher.cache.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.cache.proxy.mutableTypes.MutableMethod.Companion.toMutable
import org.jf.dexlib2.base.reference.BaseTypeReference
import org.jf.dexlib2.iface.ClassDef

class MutableClass(classDef: ClassDef) : ClassDef, BaseTypeReference() {
// Class
private var type = classDef.type
private var sourceFile = classDef.sourceFile
private var accessFlags = classDef.accessFlags
private var superclass = classDef.superclass

private val interfaces = classDef.interfaces.toMutableList()
private val annotations = classDef.annotations.map { annotation -> annotation.toMutable() }.toMutableSet()

// Methods
private val methods = classDef.methods.map { method -> method.toMutable() }.toMutableSet()
private val directMethods = classDef.directMethods.map { directMethod -> directMethod.toMutable() }.toMutableSet()
private val virtualMethods =
classDef.virtualMethods.map { virtualMethod -> virtualMethod.toMutable() }.toMutableSet()

// Fields
private val fields = classDef.fields.map { field -> field.toMutable() }.toMutableSet()
private val staticFields = classDef.staticFields.map { staticField -> staticField.toMutable() }.toMutableSet()
private val instanceFields =
classDef.instanceFields.map { instanceFields -> instanceFields.toMutable() }.toMutableSet()

fun setType(type: String) {
this.type = type
}

fun setSourceFile(sourceFile: String?) {
this.sourceFile = sourceFile
}

fun setAccessFlags(accessFlags: Int) {
this.accessFlags = accessFlags
}

fun setSuperClass(superclass: String?) {
this.superclass = superclass
}

override fun getType(): String {
return type
}

override fun getAccessFlags(): Int {
return accessFlags
}

override fun getSourceFile(): String? {
return sourceFile
}

override fun getSuperclass(): String? {
return superclass
}

override fun getInterfaces(): MutableList<String> {
return interfaces
}

override fun getAnnotations(): MutableSet<MutableAnnotation> {
return annotations
}

override fun getStaticFields(): MutableSet<MutableField> {
return staticFields
}

override fun getInstanceFields(): MutableSet<MutableField> {
return instanceFields
}

override fun getFields(): MutableSet<MutableField> {
return fields
}

override fun getDirectMethods(): MutableSet<MutableMethod> {
return directMethods
}

override fun getVirtualMethods(): MutableSet<MutableMethod> {
return virtualMethods
}

override fun getMethods(): MutableSet<MutableMethod> {
return methods
}
}
@@ -0,0 +1,26 @@
package app.revanced.patcher.cache.proxy.mutableTypes

import org.jf.dexlib2.iface.value.EncodedValue

class MutableEncodedValue(encodedValue: EncodedValue) : EncodedValue {
private var valueType = encodedValue.valueType

fun setValueType(valueType: Int) {
this.valueType = valueType
}

override fun compareTo(other: EncodedValue): Int {
return valueType - other.valueType

}

override fun getValueType(): Int {
return valueType
}

companion object {
fun EncodedValue.toMutable(): MutableEncodedValue {
return MutableEncodedValue(this)
}
}
}

0 comments on commit 3651981

Please sign in to comment.