fix build system issues

This commit is contained in:
oSumAtrIX
2026-01-28 20:10:33 +01:00
parent c65d8d692a
commit b7dfa1d99f
51 changed files with 681 additions and 485 deletions

View File

@@ -1,5 +1,5 @@
import com.android.build.api.dsl.androidLibrary
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.abi.ExperimentalAbiValidation
plugins {
@@ -33,12 +33,6 @@ kotlin {
withDeviceTestBuilder {
sourceSetTreeName = "test"
}
compilations.configureEach {
compilerOptions.configure {
jvmTarget.set(JvmTarget.JVM_11)
}
}
}
sourceSets {
@@ -61,6 +55,8 @@ kotlin {
"-Xexplicit-backing-fields",
"-Xcontext-parameters",
)
jvmToolchain(11)
}
}

View File

@@ -0,0 +1,7 @@
package app.revanced.collections
actual fun <K, V> MutableMap<K, V>.kmpMerge(
key: K,
value: V,
remappingFunction: (oldValue: V, newValue: V) -> V,
) = MutableMap<K, V>::merge.call(key, value, remappingFunction) as Unit

View File

@@ -1,8 +1,12 @@
package java.io
package app.revanced.java.io
import java.io.File
import java.nio.charset.Charset
internal actual fun File.kmpResolve(child: String) = resolve(child)
internal actual fun File.kmpDeleteRecursively() = deleteRecursively()
internal actual fun File.kmpInputStream() = inputStream()
internal actual fun File.kmpBufferedWriter(charset: Charset) = bufferedWriter(charset)
internal actual fun File.kmpBufferedWriter(charset: Charset) = bufferedWriter(charset)

View File

@@ -25,7 +25,9 @@ actual fun loadPatches(
) = loadPatches(
patchesFiles = patchesFiles,
{ patchBundle ->
MultiDexIO.readDexFile(true, patchBundle, BasicDexFileNamer(), null, null).classes
MultiDexIO
.readDexFile(true, patchBundle, BasicDexFileNamer(), null, null)
.classes
.map { classDef ->
classDef.type.substring(1, classDef.length - 1)
}
@@ -33,7 +35,11 @@ actual fun loadPatches(
DexClassLoader(
patchesFiles.joinToString(File.pathSeparator) { it.absolutePath },
null,
null, null
null,
null,
),
onFailedToLoad
onFailedToLoad,
)
@Suppress("NOTHING_TO_INLINE")
actual inline val currentClassLoader get() = object {}::class.java.classLoader

View File

@@ -1,7 +0,0 @@
package collections
actual fun <K, V> MutableMap<K, V>.kmpMerge(
key: K,
value: V,
remappingFunction: (oldValue: V, newValue: V) -> V
) = merge(key, value, remappingFunction)

View File

@@ -1,4 +1,4 @@
package collections
package app.revanced.collections
internal expect fun <K, V> MutableMap<K, V>.kmpMerge(
key: K,
@@ -10,4 +10,4 @@ internal fun <K, V> MutableMap<K, V>.merge(
key: K,
value: V,
remappingFunction: (oldValue: V, newValue: V) -> V,
) = kmpMerge(key, value, remappingFunction)
) = kmpMerge(key, value, remappingFunction)

View File

@@ -1,10 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableAnnotationElement.Companion.toMutable
import com.android.tools.smali.dexlib2.base.value.BaseAnnotationEncodedValue
import com.android.tools.smali.dexlib2.mutable.MutableAnnotationElement.Companion.toMutable
import com.android.tools.smali.dexlib2.iface.value.AnnotationEncodedValue
class MutableAnnotationEncodedValue(annotationEncodedValue: AnnotationEncodedValue) :
BaseAnnotationEncodedValue(),
class MutableAnnotationEncodedValue(
annotationEncodedValue: AnnotationEncodedValue,
) : BaseAnnotationEncodedValue(),
MutableEncodedValue {
private var type = annotationEncodedValue.type

View File

@@ -1,9 +1,13 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import app.revanced.com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.base.value.BaseArrayEncodedValue
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.iface.value.ArrayEncodedValue
class MutableArrayEncodedValue(arrayEncodedValue: ArrayEncodedValue) : BaseArrayEncodedValue(), MutableEncodedValue {
class MutableArrayEncodedValue(
arrayEncodedValue: ArrayEncodedValue,
) : BaseArrayEncodedValue(),
MutableEncodedValue {
private val _value by lazy {
arrayEncodedValue.value.map { encodedValue -> encodedValue.toMutable() }.toMutableList()
}

View File

@@ -1,9 +1,11 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseBooleanEncodedValue
import com.android.tools.smali.dexlib2.iface.value.BooleanEncodedValue
class MutableBooleanEncodedValue(booleanEncodedValue: BooleanEncodedValue) :
BaseBooleanEncodedValue(),
class MutableBooleanEncodedValue(
booleanEncodedValue: BooleanEncodedValue,
) : BaseBooleanEncodedValue(),
MutableEncodedValue {
private var value = booleanEncodedValue.value

View File

@@ -1,8 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseByteEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
class MutableByteEncodedValue(byteEncodedValue: ByteEncodedValue) : BaseByteEncodedValue(), MutableEncodedValue {
class MutableByteEncodedValue(
byteEncodedValue: ByteEncodedValue,
) : BaseByteEncodedValue(),
MutableEncodedValue {
private var value = byteEncodedValue.value
fun setValue(value: Byte) {

View File

@@ -1,8 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseCharEncodedValue
import com.android.tools.smali.dexlib2.iface.value.CharEncodedValue
class MutableCharEncodedValue(charEncodedValue: CharEncodedValue) : BaseCharEncodedValue(), MutableEncodedValue {
class MutableCharEncodedValue(
charEncodedValue: CharEncodedValue,
) : BaseCharEncodedValue(),
MutableEncodedValue {
private var value = charEncodedValue.value
fun setValue(value: Char) {

View File

@@ -1,9 +1,11 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseDoubleEncodedValue
import com.android.tools.smali.dexlib2.iface.value.DoubleEncodedValue
class MutableDoubleEncodedValue(doubleEncodedValue: DoubleEncodedValue) :
BaseDoubleEncodedValue(),
class MutableDoubleEncodedValue(
doubleEncodedValue: DoubleEncodedValue,
) : BaseDoubleEncodedValue(),
MutableEncodedValue {
private var value = doubleEncodedValue.value

View File

@@ -1,11 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.ValueType
import com.android.tools.smali.dexlib2.iface.value.*
interface MutableEncodedValue : EncodedValue {
companion object {
fun EncodedValue.toMutable(): MutableEncodedValue {
return when (this.valueType) {
fun EncodedValue.toMutable(): MutableEncodedValue =
when (this.valueType) {
ValueType.TYPE -> MutableTypeEncodedValue(this as TypeEncodedValue)
ValueType.FIELD -> MutableFieldEncodedValue(this as FieldEncodedValue)
ValueType.METHOD -> MutableMethodEncodedValue(this as MethodEncodedValue)
@@ -26,6 +27,5 @@ interface MutableEncodedValue : EncodedValue {
ValueType.NULL -> MutableNullEncodedValue()
else -> this as MutableEncodedValue
}
}
}
}

View File

@@ -1,9 +1,13 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseEnumEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.value.EnumEncodedValue
class MutableEnumEncodedValue(enumEncodedValue: EnumEncodedValue) : BaseEnumEncodedValue(), MutableEncodedValue {
class MutableEnumEncodedValue(
enumEncodedValue: EnumEncodedValue,
) : BaseEnumEncodedValue(),
MutableEncodedValue {
private var value = enumEncodedValue.value
fun setValue(value: FieldReference) {

View File

@@ -1,10 +1,14 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.ValueType
import com.android.tools.smali.dexlib2.base.value.BaseFieldEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.value.FieldEncodedValue
class MutableFieldEncodedValue(fieldEncodedValue: FieldEncodedValue) : BaseFieldEncodedValue(), MutableEncodedValue {
class MutableFieldEncodedValue(
fieldEncodedValue: FieldEncodedValue,
) : BaseFieldEncodedValue(),
MutableEncodedValue {
private var value = fieldEncodedValue.value
fun setValue(value: FieldReference) {

View File

@@ -1,8 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseFloatEncodedValue
import com.android.tools.smali.dexlib2.iface.value.FloatEncodedValue
class MutableFloatEncodedValue(floatEncodedValue: FloatEncodedValue) : BaseFloatEncodedValue(), MutableEncodedValue {
class MutableFloatEncodedValue(
floatEncodedValue: FloatEncodedValue,
) : BaseFloatEncodedValue(),
MutableEncodedValue {
private var value = floatEncodedValue.value
fun setValue(value: Float) {

View File

@@ -1,8 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseIntEncodedValue
import com.android.tools.smali.dexlib2.iface.value.IntEncodedValue
class MutableIntEncodedValue(intEncodedValue: IntEncodedValue) : BaseIntEncodedValue(), MutableEncodedValue {
class MutableIntEncodedValue(
intEncodedValue: IntEncodedValue,
) : BaseIntEncodedValue(),
MutableEncodedValue {
private var value = intEncodedValue.value
fun setValue(value: Int) {

View File

@@ -1,8 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseLongEncodedValue
import com.android.tools.smali.dexlib2.iface.value.LongEncodedValue
class MutableLongEncodedValue(longEncodedValue: LongEncodedValue) : BaseLongEncodedValue(), MutableEncodedValue {
class MutableLongEncodedValue(
longEncodedValue: LongEncodedValue,
) : BaseLongEncodedValue(),
MutableEncodedValue {
private var value = longEncodedValue.value
fun setValue(value: Long) {

View File

@@ -1,10 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseMethodEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.value.MethodEncodedValue
class MutableMethodEncodedValue(methodEncodedValue: MethodEncodedValue) :
BaseMethodEncodedValue(),
class MutableMethodEncodedValue(
methodEncodedValue: MethodEncodedValue,
) : BaseMethodEncodedValue(),
MutableEncodedValue {
private var value = methodEncodedValue.value

View File

@@ -1,10 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseMethodHandleEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodHandleReference
import com.android.tools.smali.dexlib2.iface.value.MethodHandleEncodedValue
class MutableMethodHandleEncodedValue(methodHandleEncodedValue: MethodHandleEncodedValue) :
BaseMethodHandleEncodedValue(),
class MutableMethodHandleEncodedValue(
methodHandleEncodedValue: MethodHandleEncodedValue,
) : BaseMethodHandleEncodedValue(),
MutableEncodedValue {
private var value = methodHandleEncodedValue.value

View File

@@ -1,9 +1,13 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseMethodTypeEncodedValue
import com.android.tools.smali.dexlib2.iface.reference.MethodProtoReference
import com.android.tools.smali.dexlib2.iface.value.MethodTypeEncodedValue
class MutableMethodTypeEncodedValue(methodTypeEncodedValue: MethodTypeEncodedValue) : BaseMethodTypeEncodedValue(), MutableEncodedValue {
class MutableMethodTypeEncodedValue(
methodTypeEncodedValue: MethodTypeEncodedValue,
) : BaseMethodTypeEncodedValue(),
MutableEncodedValue {
private var value = methodTypeEncodedValue.value
fun setValue(value: MethodProtoReference) {

View File

@@ -0,0 +1,12 @@
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseNullEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
class MutableNullEncodedValue :
BaseNullEncodedValue(),
MutableEncodedValue {
companion object {
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue = MutableByteEncodedValue(this)
}
}

View File

@@ -1,9 +1,11 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseShortEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ShortEncodedValue
class MutableShortEncodedValue(shortEncodedValue: ShortEncodedValue) :
BaseShortEncodedValue(),
class MutableShortEncodedValue(
shortEncodedValue: ShortEncodedValue,
) : BaseShortEncodedValue(),
MutableEncodedValue {
private var value = shortEncodedValue.value

View File

@@ -1,9 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseStringEncodedValue
import com.android.tools.smali.dexlib2.iface.value.ByteEncodedValue
import com.android.tools.smali.dexlib2.iface.value.StringEncodedValue
class MutableStringEncodedValue(stringEncodedValue: StringEncodedValue) :
BaseStringEncodedValue(),
class MutableStringEncodedValue(
stringEncodedValue: StringEncodedValue,
) : BaseStringEncodedValue(),
MutableEncodedValue {
private var value = stringEncodedValue.value

View File

@@ -1,8 +1,12 @@
package com.android.tools.smali.dexlib2.iface.value
package app.revanced.com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseTypeEncodedValue
import com.android.tools.smali.dexlib2.iface.value.TypeEncodedValue
class MutableTypeEncodedValue(typeEncodedValue: TypeEncodedValue) : BaseTypeEncodedValue(), MutableEncodedValue {
class MutableTypeEncodedValue(
typeEncodedValue: TypeEncodedValue,
) : BaseTypeEncodedValue(),
MutableEncodedValue {
private var value = typeEncodedValue.value
fun setValue(value: String) {

View File

@@ -1,10 +1,12 @@
package com.android.tools.smali.dexlib2.mutable
package app.revanced.com.android.tools.smali.dexlib2.mutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableAnnotationElement.Companion.toMutable
import com.android.tools.smali.dexlib2.base.BaseAnnotation
import com.android.tools.smali.dexlib2.iface.Annotation
import com.android.tools.smali.dexlib2.mutable.MutableAnnotationElement.Companion.toMutable
class MutableAnnotation(annotation: Annotation) : BaseAnnotation() {
class MutableAnnotation(
annotation: Annotation,
) : BaseAnnotation() {
private val visibility = annotation.visibility
private val type = annotation.type
private val _elements by lazy { annotation.elements.map { element -> element.toMutable() }.toMutableSet() }

View File

@@ -1,11 +1,13 @@
package com.android.tools.smali.dexlib2.mutable
package app.revanced.com.android.tools.smali.dexlib2.mutable
import app.revanced.com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue
import app.revanced.com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.base.BaseAnnotationElement
import com.android.tools.smali.dexlib2.iface.AnnotationElement
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
class MutableAnnotationElement(annotationElement: AnnotationElement) : BaseAnnotationElement() {
class MutableAnnotationElement(
annotationElement: AnnotationElement,
) : BaseAnnotationElement() {
private var name = annotationElement.name
private var value = annotationElement.value.toMutable()

View File

@@ -1,14 +1,17 @@
package com.android.tools.smali.dexlib2.mutable
package app.revanced.com.android.tools.smali.dexlib2.mutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableField.Companion.toMutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import com.android.tools.smali.dexlib2.base.reference.BaseTypeReference
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
import com.android.tools.smali.dexlib2.mutable.MutableField.Companion.toMutable
import com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import com.android.tools.smali.dexlib2.util.FieldUtil
import com.android.tools.smali.dexlib2.util.MethodUtil
class MutableClassDef(classDef: ClassDef) : BaseTypeReference(), ClassDef {
class MutableClassDef(
classDef: ClassDef,
) : BaseTypeReference(),
ClassDef {
// Class
private var type = classDef.type
private var sourceFile = classDef.sourceFile

View File

@@ -1,12 +1,15 @@
package com.android.tools.smali.dexlib2.mutable
package app.revanced.com.android.tools.smali.dexlib2.mutable
import app.revanced.com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue
import app.revanced.com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
import com.android.tools.smali.dexlib2.base.reference.BaseFieldReference
import com.android.tools.smali.dexlib2.iface.Field
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue
import com.android.tools.smali.dexlib2.iface.value.MutableEncodedValue.Companion.toMutable
import com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
class MutableField(field: Field) : BaseFieldReference(), Field {
class MutableField(
field: Field,
) : BaseFieldReference(),
Field {
private var definingClass = field.definingClass
private var name = field.name
private var type = field.type

View File

@@ -1,12 +1,15 @@
package com.android.tools.smali.dexlib2.mutable
package app.revanced.com.android.tools.smali.dexlib2.mutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethodParameter.Companion.toMutable
import com.android.tools.smali.dexlib2.base.reference.BaseMethodReference
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
import com.android.tools.smali.dexlib2.mutable.MutableMethodParameter.Companion.toMutable
class MutableMethod(method: Method) : BaseMethodReference(), Method {
class MutableMethod(
method: Method,
) : BaseMethodReference(),
Method {
private var definingClass = method.definingClass
private var name = method.name
private var accessFlags = method.accessFlags

View File

@@ -1,10 +1,13 @@
package com.android.tools.smali.dexlib2.mutable
package app.revanced.com.android.tools.smali.dexlib2.mutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
import com.android.tools.smali.dexlib2.base.BaseMethodParameter
import com.android.tools.smali.dexlib2.iface.MethodParameter
import com.android.tools.smali.dexlib2.mutable.MutableAnnotation.Companion.toMutable
class MutableMethodParameter(parameter: MethodParameter) : BaseMethodParameter(), MethodParameter {
class MutableMethodParameter(
parameter: MethodParameter,
) : BaseMethodParameter(),
MethodParameter {
private var type = parameter.type
private var name = parameter.name
private var signature = parameter.signature

View File

@@ -0,0 +1,14 @@
package app.revanced.java.io
import java.io.BufferedWriter
import java.io.File
import java.io.FileInputStream
import java.nio.charset.Charset
internal expect fun File.kmpResolve(child: String): File
internal expect fun File.kmpDeleteRecursively(): Boolean
internal expect fun File.kmpInputStream(): FileInputStream
internal expect fun File.kmpBufferedWriter(charset: Charset): BufferedWriter

View File

@@ -2,6 +2,7 @@
package app.revanced.patcher
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.patcher.extensions.*
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.AccessFlags
@@ -13,7 +14,6 @@ import com.android.tools.smali.dexlib2.iface.instruction.*
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.mutable.MutableMethod
import com.android.tools.smali.dexlib2.util.MethodUtil
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
@@ -131,8 +131,7 @@ fun Iterable<Method>.firstMutableMethodOrNull(methodReference: MethodReference)
@JvmName("firstMutableMethodInMethods")
context(_: BytecodePatchContext)
fun Iterable<Method>.firstMutableMethod(methodReference: MethodReference) =
requireNotNull(firstMutableMethodOrNull(methodReference))
fun Iterable<Method>.firstMutableMethod(methodReference: MethodReference) = requireNotNull(firstMutableMethodOrNull(methodReference))
@JvmName("firstMethodOrNullInMethods")
fun Iterable<Method>.firstMethodOrNull(
@@ -210,8 +209,7 @@ fun Iterable<ClassDef>.firstMethodOrNull(methodReference: MethodReference) =
asSequence().flatMap { it.methods.asSequence() }.asIterable().firstMethodOrNull(methodReference)
@JvmName("firstMethodInClassDefs")
fun Iterable<ClassDef>.firstMethod(methodReference: MethodReference) =
requireNotNull(firstMethodOrNull(methodReference))
fun Iterable<ClassDef>.firstMethod(methodReference: MethodReference) = requireNotNull(firstMethodOrNull(methodReference))
@JvmName("firstMutableMethodOrNullInClassDefs")
context(_: BytecodePatchContext)
@@ -220,8 +218,7 @@ fun Iterable<ClassDef>.firstMutableMethodOrNull(methodReference: MethodReference
@JvmName("firstMutableMethodInClassDefs")
context(_: BytecodePatchContext)
fun Iterable<ClassDef>.firstMutableMethod(methodReference: MethodReference) =
requireNotNull(firstMutableMethodOrNull(methodReference))
fun Iterable<ClassDef>.firstMutableMethod(methodReference: MethodReference) = requireNotNull(firstMutableMethodOrNull(methodReference))
@JvmName("firstMethodOrNullInClassDefs")
fun Iterable<ClassDef>.firstMethodOrNull(predicate: MethodPredicate = { true }) =
@@ -296,13 +293,11 @@ fun ClassDef.firstMethod(methodReference: MethodReference) = requireNotNull(firs
@JvmName("firstMutableMethodOrNullInClassDef")
context(_: BytecodePatchContext)
fun ClassDef.firstMutableMethodOrNull(methodReference: MethodReference) =
methods.firstMutableMethodOrNull(methodReference)
fun ClassDef.firstMutableMethodOrNull(methodReference: MethodReference) = methods.firstMutableMethodOrNull(methodReference)
@JvmName("firstMutableMethodInClassDef")
context(_: BytecodePatchContext)
fun ClassDef.firstMutableMethod(methodReference: MethodReference) =
requireNotNull(firstMutableMethodOrNull(methodReference))
fun ClassDef.firstMutableMethod(methodReference: MethodReference) = requireNotNull(firstMutableMethodOrNull(methodReference))
@JvmName("firstMethodOrNullInClassDef")
fun ClassDef.firstMethodOrNull(
@@ -481,8 +476,7 @@ fun gettingFirstMethodOrNull(method: MethodReference) = cachedReadOnlyProperty {
fun gettingFirstMethod(method: MethodReference) = cachedReadOnlyProperty { firstMethod(method) }
fun gettingFirstMutableMethodOrNull(method: MethodReference) =
cachedReadOnlyProperty { firstMutableMethodOrNull(method) }
fun gettingFirstMutableMethodOrNull(method: MethodReference) = cachedReadOnlyProperty { firstMutableMethodOrNull(method) }
fun gettingFirstMutableMethod(method: MethodReference) = cachedReadOnlyProperty { firstMutableMethod(method) }
@@ -959,9 +953,9 @@ fun MutablePredicateList<Method>.definingClass(
fun MutablePredicateList<Method>.parameterTypes(vararg parameterTypePrefixes: String) =
predicate {
parameterTypes.size == parameterTypePrefixes.size &&
parameterTypes
.zip(parameterTypePrefixes)
.all { (a, b) -> a.startsWith(b) }
parameterTypes
.zip(parameterTypePrefixes)
.all { (a, b) -> a.startsWith(b) }
}
fun MutablePredicateList<Method>.strings(build: Function<IndexedMatcher<Instruction>>) {
@@ -1039,8 +1033,7 @@ fun MutablePredicateList<Method>.opcodes(vararg opcodes: Opcode) = instructions
inline fun <reified T : Instruction> `is`(crossinline predicate: Predicate<T> = { true }): IndexedMatcherPredicate<Instruction> =
{ _, _, _ -> (this as? T)?.predicate() == true }
fun instruction(predicate: Predicate<Instruction> = { true }): IndexedMatcherPredicate<Instruction> =
{ _, _, _ -> predicate() }
fun instruction(predicate: Predicate<Instruction> = { true }): IndexedMatcherPredicate<Instruction> = { _, _, _ -> predicate() }
fun registers(predicate: Predicate<IntArray> = { true }): IndexedMatcherPredicate<Instruction> =
{ _, _, _ ->
@@ -1084,8 +1077,7 @@ fun registers(
},
) = registers({ compare(registers) })
fun literal(predicate: Predicate<Long> = { true }): IndexedMatcherPredicate<Instruction> =
{ _, _, _ -> wideLiteral?.predicate() == true }
fun literal(predicate: Predicate<Long> = { true }): IndexedMatcherPredicate<Instruction> = { _, _, _ -> wideLiteral?.predicate() == true }
fun literal(
literal: Long,
@@ -1112,8 +1104,7 @@ fun field(
compare: String.(String) -> Boolean = String::equals,
) = field { this.name.compare(name) }
fun type(predicate: Predicate<String> = { true }): IndexedMatcherPredicate<Instruction> =
{ _, _, _ -> type?.predicate() == true }
fun type(predicate: Predicate<String> = { true }): IndexedMatcherPredicate<Instruction> = { _, _, _ -> type?.predicate() == true }
fun type(
type: String,
@@ -1158,13 +1149,13 @@ operator fun String.invoke(compare: String.(String) -> Boolean = String::equals)
operator fun Opcode.invoke(): IndexedMatcherPredicate<Instruction> = { _, _, _ -> opcode == this@invoke }
typealias BuildCompositeDeclarativePredicate<Method> =
context(
context(
BytecodePatchContext,
PredicateContext,
IndexedMatcher<Instruction>,
MutableList<String>
)
MutablePredicateList<Method>.() -> Unit
)
MutablePredicateList<Method>.() -> Unit
fun firstMethodComposite(
vararg strings: String,

View File

@@ -1,10 +1,10 @@
package app.revanced.patcher
import app.revanced.java.io.kmpDeleteRecursively
import app.revanced.java.io.kmpResolve
import app.revanced.patcher.patch.*
import java.io.File
import java.io.InputStream
import java.io.deleteRecursively
import java.io.resolve
import java.util.logging.Logger
fun patcher(
@@ -19,20 +19,22 @@ fun patcher(
if (temporaryFilesPath.exists()) {
logger.info("Deleting existing temporary files directory")
if (!temporaryFilesPath.deleteRecursively())
if (!temporaryFilesPath.kmpDeleteRecursively()) {
logger.severe("Failed to delete existing temporary files directory")
}
}
val apkFilesPath = temporaryFilesPath.resolve("apk").also { it.mkdirs() }
val patchedFilesPath = temporaryFilesPath.resolve("patched").also { it.mkdirs() }
val apkFilesPath = temporaryFilesPath.kmpResolve("apk").also { it.mkdirs() }
val patchedFilesPath = temporaryFilesPath.kmpResolve("patched").also { it.mkdirs() }
val resourcePatchContext = ResourcePatchContext(
apkFile,
apkFilesPath,
patchedFilesPath,
aaptBinaryPath,
frameworkFileDirectory
)
val resourcePatchContext =
ResourcePatchContext(
apkFile,
apkFilesPath,
patchedFilesPath,
aaptBinaryPath,
frameworkFileDirectory,
)
val (packageName, versionName) = resourcePatchContext.decodeManifest()
val patches = getPatches(packageName, versionName)
@@ -41,10 +43,11 @@ fun patcher(
if (patches.any { patch -> patch.patchesResources }) resourcePatchContext.decodeResources()
// After initializing the resource context, to keep memory usage time low.
val bytecodePatchContext = BytecodePatchContext(
apkFile,
patchedFilesPath
)
val bytecodePatchContext =
BytecodePatchContext(
apkFile,
patchedFilesPath,
)
logger.info("Warming up the cache")
@@ -60,7 +63,7 @@ fun patcher(
fun Set<Patch>.apply(
bytecodePatchContext: BytecodePatchContext,
resourcePatchContext: ResourcePatchContext,
emit: (PatchResult) -> Unit
emit: (PatchResult) -> Unit,
): PatchesResult {
val appliedPatches = LinkedHashMap<Patch, PatchResult>()
@@ -70,17 +73,23 @@ fun Set<Patch>.apply(
return if (result == null) {
val failedDependency = dependencies.asSequence().map { it.apply() }.firstOrNull { it.exception != null }
if (failedDependency != null) return patchResult(
"The dependant patch \"$failedDependency\" of the patch \"$this\" raised an exception:\n" +
if (failedDependency != null) {
return patchResult(
"The dependant patch \"${failedDependency.patch}\" of the patch \"$this\" raised an exception:\n" +
failedDependency.exception!!.stackTraceToString(),
)
)
}
val exception = runCatching { apply(bytecodePatchContext, resourcePatchContext) }
.exceptionOrNull() as? Exception
val exception =
runCatching { apply(bytecodePatchContext, resourcePatchContext) }
.exceptionOrNull() as? Exception
patchResult(exception).also { result -> appliedPatches[this] = result }
} else if (result.exception == null) result
else patchResult("The patch '$this' has failed previously")
} else if (result.exception == null) {
result
} else {
patchResult("The patch '$this' has failed previously")
}
}
val patchResult = patch.apply()
@@ -91,9 +100,10 @@ fun Set<Patch>.apply(
}
}
val succeededPatchesWithFinalizeBlock = appliedPatches.values.filter {
it.exception == null && it.patch.afterDependents != null
}
val succeededPatchesWithFinalizeBlock =
appliedPatches.values.filter {
it.exception == null && it.patch.afterDependents != null
}
succeededPatchesWithFinalizeBlock.asReversed().forEach { result ->
val patch = result.patch
@@ -107,9 +117,9 @@ fun Set<Patch>.apply(
"The patch \"$patch\" raised an exception:\n" + it.stackTraceToString(),
it,
),
)
),
)
}
},
)
}
@@ -126,14 +136,16 @@ class PatchesResult internal constructor(
val dexFiles: Set<PatchedDexFile>,
val resources: PatchedResources?,
) {
/**
* A dex file.
*
* @param name The original name of the dex file.
* @param stream The dex file as [InputStream].
*/
class PatchedDexFile internal constructor(val name: String, val stream: InputStream)
class PatchedDexFile internal constructor(
val name: String,
val stream: InputStream,
)
/**
* The resources of a patched apk.

View File

@@ -1,5 +1,6 @@
package app.revanced.patcher.extensions
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcodes
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
@@ -16,9 +17,7 @@ import org.antlr.runtime.TokenSource
import org.antlr.runtime.tree.CommonTreeNodeStream
import java.io.StringReader
private inline fun <reified T : Reference> Instruction.reference(): T? =
(this as? ReferenceInstruction)?.reference as? T
private inline fun <reified T : Reference> Instruction.reference(): T? = (this as? ReferenceInstruction)?.reference as? T
val Instruction.reference: Reference?
get() = reference()
@@ -64,7 +63,6 @@ val Instruction.string
val Instruction.wideLiteral
get() = (this as? NarrowLiteralInstruction)?.wideLiteral
private const val CLASS_HEADER = ".class LInlineCompiler;\n.super Ljava/lang/Object;\n"
private const val STATIC_HEADER = "$CLASS_HEADER.method public static dummyMethod("
private const val HEADER = "$CLASS_HEADER.method public dummyMethod("
@@ -78,7 +76,7 @@ private val sb by lazy { StringBuilder(512) }
* @param templateMethod The method to compile the instructions against.
* @returns A list of instructions.
*/
fun String.toInstructions(templateMethod: com.android.tools.smali.dexlib2.mutable.MutableMethod? = null): List<BuilderInstruction> {
fun String.toInstructions(templateMethod: MutableMethod? = null): List<BuilderInstruction> {
val parameters = templateMethod?.parameterTypes?.joinToString("") { it } ?: ""
val registers = templateMethod?.implementation?.registerCount ?: 1 // TODO: Should this be 0?
val isStatic = templateMethod?.let { AccessFlags.STATIC.isSet(it.accessFlags) } ?: true
@@ -99,17 +97,21 @@ fun String.toInstructions(templateMethod: com.android.tools.smali.dexlib2.mutabl
if (lexer.numberOfSyntaxErrors > 0 || parser.numberOfSyntaxErrors > 0) {
throw IllegalStateException(
"Lexer errors: ${lexer.numberOfSyntaxErrors}, Parser errors: ${parser.numberOfSyntaxErrors}"
"Lexer errors: ${lexer.numberOfSyntaxErrors}, Parser errors: ${parser.numberOfSyntaxErrors}",
)
}
val treeStream = CommonTreeNodeStream(fileTree.tree).apply {
tokenStream = tokens
}
val treeStream =
CommonTreeNodeStream(fileTree.tree).apply {
tokenStream = tokens
}
val walker = smaliTreeWalker(treeStream)
walker.setDexBuilder(DexBuilder(Opcodes.getDefault()))
val classDef = walker.smali_file()
return classDef.methods.first().instructions.map { it as BuilderInstruction }
return classDef.methods
.first()
.instructions
.map { it as BuilderInstruction }
}

View File

@@ -1,5 +1,6 @@
package app.revanced.patcher.extensions
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.builder.BuilderInstruction
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
@@ -9,10 +10,8 @@ import com.android.tools.smali.dexlib2.builder.instruction.*
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.MethodImplementation
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.mutable.MutableMethod
fun Method.accessFlags(vararg flags: AccessFlags) =
accessFlags.and(flags.map { it.ordinal }.reduce { acc, i -> acc or i }) != 0
fun Method.accessFlags(vararg flags: AccessFlags) = accessFlags.and(flags.map { it.ordinal }.reduce { acc, i -> acc or i }) != 0
/**
* Add instructions to a method at the given index.
@@ -31,8 +30,7 @@ fun MutableMethodImplementation.addInstructions(
*
* @param instructions The instructions to add.
*/
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) =
instructions.forEach { addInstruction(it) }
fun MutableMethodImplementation.addInstructions(instructions: List<BuilderInstruction>) = instructions.forEach { addInstruction(it) }
/**
* Remove instructions from a method at the given index.
@@ -119,8 +117,7 @@ fun MutableMethod.addInstructions(
*
* @param instructions The instructions to add.
*/
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) =
implementation!!.addInstructions(instructions)
fun MutableMethod.addInstructions(instructions: List<BuilderInstruction>) = implementation!!.addInstructions(instructions)
/**
* Add instructions to a method.
@@ -137,8 +134,7 @@ fun MutableMethod.addInstructions(
*
* @param smaliInstructions The instructions to add.
*/
fun MutableMethod.addInstructions(smaliInstructions: String) =
implementation!!.addInstructions(smaliInstructions.toInstructions(this))
fun MutableMethod.addInstructions(smaliInstructions: String) = implementation!!.addInstructions(smaliInstructions.toInstructions(this))
/**
* Add instructions to a method at the given index.
@@ -155,11 +151,12 @@ fun MutableMethod.addInstructionsWithLabels(
) {
// Create reference dummy instructions for the instructions.
val nopSmali =
StringBuilder(smaliInstructions).also { builder ->
externalLabels.forEach { (name, _) ->
builder.append("\n:$name\nnop")
}
}.toString()
StringBuilder(smaliInstructions)
.also { builder ->
externalLabels.forEach { (name, _) ->
builder.append("\n:$name\nnop")
}
}.toString()
// Compile the instructions with the dummy labels
val compiledInstructions = nopSmali.toInstructions(this)
@@ -171,7 +168,9 @@ fun MutableMethod.addInstructionsWithLabels(
)
implementation!!.apply {
this@apply.instructions.subList(index, index + compiledInstructions.size - externalLabels.size)
this@apply
.instructions
.subList(index, index + compiledInstructions.size - externalLabels.size)
.forEachIndexed { compiledInstructionIndex, compiledInstruction ->
// If the compiled instruction is not an offset instruction, skip it.
if (compiledInstruction !is BuilderOffsetInstruction) return@forEachIndexed
@@ -184,26 +183,43 @@ fun MutableMethod.addInstructionsWithLabels(
fun replaceOffset(
i: BuilderOffsetInstruction,
label: Label,
): BuilderOffsetInstruction {
return when (i) {
is BuilderInstruction10t -> BuilderInstruction10t(i.opcode, label)
is BuilderInstruction20t -> BuilderInstruction20t(i.opcode, label)
is BuilderInstruction21t -> BuilderInstruction21t(i.opcode, i.registerA, label)
is BuilderInstruction22t ->
): BuilderOffsetInstruction =
when (i) {
is BuilderInstruction10t -> {
BuilderInstruction10t(i.opcode, label)
}
is BuilderInstruction20t -> {
BuilderInstruction20t(i.opcode, label)
}
is BuilderInstruction21t -> {
BuilderInstruction21t(i.opcode, i.registerA, label)
}
is BuilderInstruction22t -> {
BuilderInstruction22t(
i.opcode,
i.registerA,
i.registerB,
label,
)
}
is BuilderInstruction30t -> BuilderInstruction30t(i.opcode, label)
is BuilderInstruction31t -> BuilderInstruction31t(i.opcode, i.registerA, label)
else -> throw IllegalStateException(
"A non-offset instruction was given, this should never happen!",
)
is BuilderInstruction30t -> {
BuilderInstruction30t(i.opcode, label)
}
is BuilderInstruction31t -> {
BuilderInstruction31t(i.opcode, i.registerA, label)
}
else -> {
throw IllegalStateException(
"A non-offset instruction was given, this should never happen!",
)
}
}
}
// Create the final label.
val label = newLabelForIndex(this@apply.instructions.indexOf(this))
@@ -443,4 +459,7 @@ fun MutableMethod.newLabel(index: Int) = implementation!!.newLabelForIndex(index
* @param name The label name.
* @param instruction The instruction that this label is for.
*/
data class ExternalLabel(internal val name: String, internal val instruction: Instruction)
data class ExternalLabel(
internal val name: String,
internal val instruction: Instruction,
)

View File

@@ -1,5 +1,10 @@
package app.revanced.patcher.patch
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableClassDef
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableClassDef.Companion.toMutable
import app.revanced.java.io.kmpDeleteRecursively
import app.revanced.java.io.kmpInputStream
import app.revanced.java.io.kmpResolve
import app.revanced.patcher.PatchesResult
import app.revanced.patcher.extensions.instructionsOrNull
import app.revanced.patcher.extensions.string
@@ -9,13 +14,12 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.DexFile
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.mutable.MutableClassDef
import com.android.tools.smali.dexlib2.mutable.MutableClassDef.Companion.toMutable
import lanchon.multidexlib2.BasicDexFileNamer
import lanchon.multidexlib2.DexIO
import lanchon.multidexlib2.MultiDexIO
import lanchon.multidexlib2.RawDexIO
import java.io.*
import java.io.File
import java.io.InputStream
import java.util.logging.Logger
import kotlin.reflect.jvm.jvmName
@@ -34,9 +38,8 @@ class BytecodePatchContext internal constructor(
inner class ClassDefs private constructor(
dexFile: DexFile,
private val classDefs: MutableSet<ClassDef> = dexFile.classes.toMutableSet()
) :
MutableSet<ClassDef> by classDefs {
private val classDefs: MutableSet<ClassDef> = dexFile.classes.toMutableSet(),
) : MutableSet<ClassDef> by classDefs {
private val byType = mutableMapOf<String, ClassDef>()
operator fun get(name: String): ClassDef? = byType[name]
@@ -58,8 +61,8 @@ class BytecodePatchContext internal constructor(
apkFile,
BasicDexFileNamer(),
null,
null
)
null,
),
)
internal val opcodes = dexFile.opcodes
@@ -113,8 +116,7 @@ class BytecodePatchContext internal constructor(
return anyRemoved
}
override fun retainAll(elements: Collection<ClassDef>) =
removeAll(classDefs.asSequence().filter { it !in elements })
override fun retainAll(elements: Collection<ClassDef>) = removeAll(classDefs.asSequence().filter { it !in elements })
private fun addCache(classDef: ClassDef) {
byType[classDef.type] = classDef
@@ -131,15 +133,16 @@ class BytecodePatchContext internal constructor(
byType -= classDef.type
classDef.forEachString { method, string ->
if (_methodsByStrings[string]?.also { it -= method }?.isEmpty() == true)
if (_methodsByStrings[string]?.also { it -= method }?.isEmpty() == true) {
_methodsByStrings -= string
}
}
}
private fun ClassDef.forEachString(action: (Method, String) -> Unit) {
methods.asSequence().forEach { method ->
method.instructionsOrNull?.asSequence()
method.instructionsOrNull
?.asSequence()
?.mapNotNull { it.string }
?.forEach { string -> action(method, string) }
}
@@ -184,16 +187,15 @@ class BytecodePatchContext internal constructor(
* @param extensionInputStream The input stream for an extension dex file.
*/
internal fun extendWith(extensionInputStream: InputStream) {
RawDexIO.readRawDexFile(
extensionInputStream, 0, null
).classes.forEach { classDef ->
val existingClass = classDefs[classDef.type] ?: run {
logger.fine { "Adding class \"$classDef\"" }
RawDexIO.readRawDexFile(extensionInputStream, 0, null).classes.forEach { classDef ->
val existingClass =
classDefs[classDef.type] ?: run {
logger.fine { "Adding class \"$classDef\"" }
classDefs += classDef
classDefs += classDef
return@forEach
}
return@forEach
}
logger.fine { "Class \"$classDef\" exists already. Adding missing methods and fields." }
@@ -232,32 +234,36 @@ class BytecodePatchContext internal constructor(
System.gc()
val patchedDexFileResults =
patchedFilesPath.resolve("dex").also {
it.deleteRecursively() // Make sure the directory is empty.
it.mkdirs()
}.apply {
MultiDexIO.writeDexFile(
true,
-1,
this,
BasicDexFileNamer(),
object : DexFile {
override fun getClasses() = classDefs.let {
// More performant according to
// https://github.com/LisoUseInAIKyrios/revanced-patcher/
// commit/8c26ad08457fb1565ea5794b7930da42a1c81cf1
// #diff-be698366d9868784ecf7da3fd4ac9d2b335b0bb637f9f618fbe067dbd6830b8fR197
// TODO: Benchmark, if actually faster.
HashSet<ClassDef>(it.size * 3 / 2).apply { addAll(it) }
}
patchedFilesPath
.kmpResolve("dex")
.also {
it.kmpDeleteRecursively() // Make sure the directory is empty.
it.mkdirs()
}.apply {
MultiDexIO.writeDexFile(
true,
-1,
this,
BasicDexFileNamer(),
object : DexFile {
override fun getClasses() =
classDefs.let {
// More performant according to
// https://github.com/LisoUseInAIKyrios/revanced-patcher/
// commit/8c26ad08457fb1565ea5794b7930da42a1c81cf1
// #diff-be698366d9868784ecf7da3fd4ac9d2b335b0bb637f9f618fbe067dbd6830b8fR197
// TODO: Benchmark, if actually faster.
HashSet<ClassDef>(it.size * 3 / 2).apply { addAll(it) }
}
override fun getOpcodes() = classDefs.opcodes
},
DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
) { _, entryName, _ -> logger.info { "Compiled $entryName" } }
}.listFiles { it.isFile }!!.map {
PatchesResult.PatchedDexFile(it.name, it.inputStream())
}.toSet()
override fun getOpcodes() = classDefs.opcodes
},
DexIO.DEFAULT_MAX_DEX_POOL_SIZE,
) { _, entryName, _ -> logger.info { "Compiled $entryName" } }
}.listFiles { it.isFile }!!
.map {
PatchesResult.PatchedDexFile(it.name, it.kmpInputStream())
}.toSet()
return patchedDexFileResults
}

View File

@@ -14,10 +14,12 @@ typealias PackageName = String
typealias VersionName = String
typealias Package = Pair<PackageName, Set<VersionName>?>
enum class PatchType(internal val prefix: String) {
enum class PatchType(
internal val prefix: String,
) {
BYTECODE("Bytecode"),
RAW_RESOURCE("RawResource"),
RESOURCE("Resource")
RESOURCE("Resource"),
}
internal val Patch.patchesResources: Boolean get() = type == PatchType.RESOURCE || dependencies.any { it.patchesResources }
@@ -32,7 +34,10 @@ open class Patch internal constructor(
internal val apply: context(BytecodePatchContext, ResourcePatchContext) () -> Unit,
// Must be nullable, so that Patcher.invoke can check,
// if a patch has an "afterDependents" in order to not emit it twice.
internal var afterDependents: (context(BytecodePatchContext, ResourcePatchContext) () -> Unit)?,
internal var afterDependents: (
context(BytecodePatchContext, ResourcePatchContext)
() -> Unit
)?,
internal val type: PatchType,
) {
val options = Options(options)
@@ -42,29 +47,30 @@ open class Patch internal constructor(
sealed class PatchBuilder<C : PatchContext<*>>(
private val type: PatchType,
private val getPatchContext: context(BytecodePatchContext, ResourcePatchContext) () -> C
) {
private var compatiblePackages: MutableSet<Package>? = null
private val dependencies = mutableSetOf<Patch>()
private val options = mutableSetOf<Option<*>>()
internal var apply: context(BytecodePatchContext, ResourcePatchContext) () -> Unit = { }
internal var afterDependents: (context(BytecodePatchContext, ResourcePatchContext) () -> Unit)? = null
internal var apply: context(BytecodePatchContext, ResourcePatchContext)
() -> Unit = { }
internal var afterDependents: (
context(BytecodePatchContext, ResourcePatchContext)
() -> Unit
)? = null
context(_: BytecodePatchContext, _: ResourcePatchContext)
private val patchContext get() = getPatchContext()
abstract val context: C
fun apply(block: C.() -> Unit) {
apply = { block(patchContext) }
open fun apply(block: C.() -> Unit) {
apply = { block(context) }
}
fun afterDependents(block: C.() -> Unit) {
afterDependents = { block(patchContext) }
afterDependents = { block(context) }
}
operator fun <T> Option<T>.invoke() = apply {
options += this
}
operator fun <T> Option<T>.invoke() = apply { options += this }
operator fun String.invoke(vararg versions: VersionName) = invoke(versions.toSet())
@@ -84,8 +90,11 @@ sealed class PatchBuilder<C : PatchContext<*>>(
dependencies += patches
}
fun build(name: String?, description: String?, use: Boolean) = Patch(
fun build(
name: String?,
description: String?,
use: Boolean,
) = Patch(
name,
description,
use,
@@ -98,33 +107,48 @@ sealed class PatchBuilder<C : PatchContext<*>>(
)
}
expect inline val currentClassLoader: ClassLoader
class BytecodePatchBuilder private constructor(
private var extensionInputStream: InputStream? = null
) : PatchBuilder<BytecodePatchContext>(
PatchType.BYTECODE,
{
// Extend the context with the extension, before returning it to the patch before applying it.
contextOf<BytecodePatchContext>().apply {
if (extensionInputStream != null) extendWith(extensionInputStream)
}
}
) {
@PublishedApi
internal var getExtensionInputStream: (() -> InputStream)? = null,
) : PatchBuilder<BytecodePatchContext>(PatchType.BYTECODE) {
internal constructor() : this(null)
fun extendWith(extension: String) {
// Should be the classloader which loaded the patch class.
val classLoader = Class.forName(Thread.currentThread().stackTrace[2].className).classLoader!!
// Must be inline to access the patch's classloader.
@Suppress("NOTHING_TO_INLINE")
inline fun extendWith(extension: String) {
// Should be the classloader which calls this function.
val classLoader = currentClassLoader
extensionInputStream = classLoader.getResourceAsStream(extension)
?: throw PatchException("Extension \"$extension\" not found")
getExtensionInputStream = {
classLoader.getResourceAsStream(extension)
?: throw PatchException("Extension \"$extension\" not found")
}
}
context(_: BytecodePatchContext, _: ResourcePatchContext)
override val context get() = contextOf<BytecodePatchContext>()
override fun apply(block: BytecodePatchContext.() -> Unit) {
apply = {
block(
// Extend the context with the extension, before returning it to the patch before applying it.
context.apply {
getExtensionInputStream?.let { get -> extendWith(get()) }
},
)
}
}
}
open class ResourcePatchBuilder internal constructor(type: PatchType) : PatchBuilder<ResourcePatchContext>(
type,
{ contextOf<ResourcePatchContext>() }
) {
open class ResourcePatchBuilder internal constructor(
type: PatchType,
) : PatchBuilder<ResourcePatchContext>(type) {
internal constructor() : this(PatchType.RESOURCE)
context(_: BytecodePatchContext, _: ResourcePatchContext)
override val context get() = contextOf<ResourcePatchContext>()
}
class RawResourcePatchBuilder internal constructor() : ResourcePatchBuilder()
@@ -133,28 +157,28 @@ fun bytecodePatch(
name: String? = null,
description: String? = null,
use: Boolean = true,
block: BytecodePatchBuilder.() -> Unit
block: BytecodePatchBuilder.() -> Unit,
) = BytecodePatchBuilder().apply(block).build(name, description, use)
fun resourcePatch(
name: String? = null,
description: String? = null,
use: Boolean = true,
block: ResourcePatchBuilder.() -> Unit
block: ResourcePatchBuilder.() -> Unit,
) = ResourcePatchBuilder().apply(block).build(name, description, use)
fun rawResourcePatch(
name: String? = null,
description: String? = null,
use: Boolean = true,
block: RawResourcePatchBuilder.() -> Unit
block: RawResourcePatchBuilder.() -> Unit,
) = RawResourcePatchBuilder().apply(block).build(name, description, use)
private fun <B : PatchBuilder<*>> creatingPatch(
description: String? = null,
use: Boolean = true,
block: B.() -> Unit,
patchSupplier: (String?, String?, Boolean, B.() -> Unit) -> Patch
patchSupplier: (String?, String?, Boolean, B.() -> Unit) -> Patch,
) = ReadOnlyProperty<Any?, Patch> { _, property -> patchSupplier(property.name, description, use, block) }
fun creatingBytecodePatch(
@@ -181,7 +205,6 @@ fun creatingRawResourcePatch(
rawResourcePatch(name, description, use, block)
}
/**
* A common interface for contexts such as [ResourcePatchContext] and [BytecodePatchContext].
*/
@@ -194,7 +217,10 @@ sealed interface PatchContext<T> : Supplier<T>
* @param errorMessage The exception message.
* @param cause The corresponding [Throwable].
*/
class PatchException(errorMessage: String?, cause: Throwable?) : Exception(errorMessage, cause) {
class PatchException(
errorMessage: String?,
cause: Throwable?,
) : Exception(errorMessage, cause) {
constructor(errorMessage: String) : this(errorMessage, null)
constructor(cause: Throwable) : this(cause.message, cause)
}
@@ -205,7 +231,10 @@ class PatchException(errorMessage: String?, cause: Throwable?) : Exception(error
* @param patch The [Patch] that ran.
* @param exception The [PatchException] thrown, if any.
*/
class PatchResult internal constructor(val patch: Patch, val exception: PatchException? = null)
class PatchResult internal constructor(
val patch: Patch,
val exception: PatchException? = null,
)
/**
* Creates a [PatchResult] for this [Patch].
@@ -222,6 +251,7 @@ internal fun Patch.patchResult(exception: Exception? = null) = PatchResult(this,
* @return The created [PatchResult].
*/
internal fun Patch.patchResult(errorMessage: String) = PatchResult(this, PatchException(errorMessage))
private fun Exception.toPatchException() = this as? PatchException ?: PatchException(this)
/**
@@ -229,22 +259,28 @@ private fun Exception.toPatchException() = this as? PatchException ?: PatchExcep
*
* @property patchesByFile The patches mapped by their patches file.
*/
class Patches internal constructor(val patchesByFile: Map<File, Set<Patch>>) : Set<Patch>
by patchesByFile.values.flatten().toSet()
class Patches internal constructor(
val patchesByFile: Map<File, Set<Patch>>,
) : Set<Patch>
by patchesByFile.values.flatten().toSet()
// Must be internal and a separate function for testing.
@Suppress("MISSING_DEPENDENCY_IN_INFERRED_TYPE_ANNOTATION_WARNING")
internal fun getPatches(classNames: List<String>, classLoader: ClassLoader): Set<Patch> {
fun Member.isUsable() =
Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && (this !is Method || parameterCount == 0)
internal fun getPatches(
classNames: List<String>,
classLoader: ClassLoader,
): Set<Patch> {
fun Member.isUsable() = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && (this !is Method || parameterCount == 0)
fun Class<*>.getPatchFields() = fields
.filter { it.type.isPatch && it.isUsable() }
.map { it.get(null) as Patch }
fun Class<*>.getPatchFields() =
fields
.filter { it.type.isPatch && it.isUsable() }
.map { it.get(null) as Patch }
fun Class<*>.getPatchMethods() = methods
.filter { it.returnType.isPatch && it.parameterCount == 0 && it.isUsable() }
.map { it.invoke(null) as Patch }
fun Class<*>.getPatchMethods() =
methods
.filter { it.returnType.isPatch && it.parameterCount == 0 && it.isUsable() }
.map { it.invoke(null) as Patch }
return classNames
.map { classLoader.loadClass(it) }
@@ -257,13 +293,17 @@ internal fun loadPatches(
vararg patchesFiles: File,
getBinaryClassNames: (patchesFile: File) -> List<String>,
classLoader: ClassLoader,
onFailedToLoad: (File, Throwable) -> Unit
) = Patches(patchesFiles.map { file ->
file to getBinaryClassNames(file)
}.mapNotNull { (file, classNames) ->
runCatching { file to getPatches(classNames, classLoader) }
.onFailure { onFailedToLoad(file, it) }.getOrNull()
}.toMap())
onFailedToLoad: (File, Throwable) -> Unit,
) = Patches(
patchesFiles
.map { file ->
file to getBinaryClassNames(file)
}.mapNotNull { (file, classNames) ->
runCatching { file to getPatches(classNames, classLoader) }
.onFailure { onFailedToLoad(file, it) }
.getOrNull()
}.toMap(),
)
expect fun loadPatches(
vararg patchesFiles: File,

View File

@@ -1,5 +1,6 @@
package app.revanced.patcher.patch
import app.revanced.java.io.kmpResolve
import app.revanced.patcher.PatchesResult
import app.revanced.patcher.util.Document
import brut.androlib.AaptInvoker
@@ -16,7 +17,6 @@ import brut.directory.ExtFile
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.io.resolve
import java.nio.file.Files
import java.util.logging.Logger
import kotlin.reflect.jvm.jvmName
@@ -41,10 +41,11 @@ class ResourcePatchContext internal constructor(
private val logger = Logger.getLogger(ResourcePatchContext::class.jvmName)
private val resourceConfig = Config.getDefaultConfig().apply {
aaptBinary = aaptBinaryPath
frameworkDirectory = frameworkFileDirectory
}
private val resourceConfig =
Config.getDefaultConfig().apply {
aaptBinary = aaptBinaryPath
frameworkDirectory = frameworkFileDirectory
}
internal var decodingMode = ResourceDecodingMode.MANIFEST
@@ -105,18 +106,20 @@ class ResourcePatchContext internal constructor(
internal fun decodeResources() {
logger.info("Decoding resources")
val resourcesDecoder = ResourcesDecoder(resourceConfig, apkInfo).also {
it.decodeResources(apkFilesPath)
it.decodeManifest(apkFilesPath)
}
val resourcesDecoder =
ResourcesDecoder(resourceConfig, apkInfo).also {
it.decodeResources(apkFilesPath)
it.decodeManifest(apkFilesPath)
}
// Record uncompressed files to preserve their state when recompiling.
ApkDecoder(apkInfo, resourceConfig).recordUncompressedFiles(resourcesDecoder.resFileMapping)
// Get the ids of the used framework packages to include them for reference when recompiling.
apkInfo.usesFramework = UsesFramework().apply {
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
}
apkInfo.usesFramework =
UsesFramework().apply {
ids = resourcesDecoder.resTable.listFramePackages().map { it.id }
}
}
/**
@@ -127,51 +130,59 @@ class ResourcePatchContext internal constructor(
override fun get(): PatchesResult.PatchedResources {
logger.info("Compiling patched resources")
val resourcesPath = patchedFilesPath.resolve("resources").also { it.mkdirs() }
val resourcesPath = patchedFilesPath.kmpResolve("resources").also { it.mkdirs() }
val resourcesApkFile = if (decodingMode == ResourceDecodingMode.ALL) {
val resourcesApkFile = resourcesPath.resolve("resources.apk").also { it.createNewFile() }
val resourcesApkFile =
if (decodingMode == ResourceDecodingMode.ALL) {
val resourcesApkFile = resourcesPath.kmpResolve("resources.apk").also { it.createNewFile() }
val manifestFile = apkFilesPath.resolve("AndroidManifest.xml").also {
ResXmlUtils.fixingPublicAttrsInProviderAttributes(it)
val manifestFile =
apkFilesPath.kmpResolve("AndroidManifest.xml").also {
ResXmlUtils.fixingPublicAttrsInProviderAttributes(it)
}
val resPath = apkFilesPath.kmpResolve("res")
val frameworkApkFiles =
with(Framework(resourceConfig)) {
apkInfo.usesFramework.ids.map { id -> getFrameworkApk(id, null) }
}.toTypedArray()
AaptInvoker(
resourceConfig,
apkInfo,
).invoke(resourcesApkFile, manifestFile, resPath, null, null, frameworkApkFiles)
resourcesApkFile
} else {
null
}
val resPath = apkFilesPath.resolve("res")
val frameworkApkFiles = with(Framework(resourceConfig)) {
apkInfo.usesFramework.ids.map { id -> getFrameworkApk(id, null) }
}.toTypedArray()
AaptInvoker(
resourceConfig,
apkInfo
).invoke(resourcesApkFile, manifestFile, resPath, null, null, frameworkApkFiles)
resourcesApkFile
} else null
val otherFiles = apkFilesPath.listFiles()!!.filter {
// Excluded because present in resources.other.
// TODO: We are reusing apkFiles as a temporarily directory for extracting resources.
// This is not ideal as it could conflict with files such as the ones that are filtered here.
// The problem is that ResourcePatchContext#get returns a File relative to apkFiles,
// and we need to extract files to that directory.
// A solution would be to use apkFiles as the working directory for the patching process.
// Once all patches have been executed, we can move the decoded resources to a new directory.
// The filters wouldn't be needed anymore.
// For now, we assume that the files we filter here are not needed for the patching process.
it.name != "AndroidManifest.xml" &&
val otherFiles =
apkFilesPath.listFiles()!!.filter {
// Excluded because present in resources.other.
// TODO: We are reusing apkFiles as a temporarily directory for extracting resources.
// This is not ideal as it could conflict with files such as the ones that are filtered here.
// The problem is that ResourcePatchContext#get returns a File relative to apkFiles,
// and we need to extract files to that directory.
// A solution would be to use apkFiles as the working directory for the patching process.
// Once all patches have been executed, we can move the decoded resources to a new directory.
// The filters wouldn't be needed anymore.
// For now, we assume that the files we filter here are not needed for the patching process.
it.name != "AndroidManifest.xml" &&
it.name != "res" &&
// Generated by Androlib.
it.name != "build"
}
val otherResourceFiles = if (otherFiles.isNotEmpty()) {
// Move the other resources files.
resourcesPath.resolve("other").also { it.mkdirs() }.apply {
otherFiles.forEach { file ->
Files.move(file.toPath(), resolve(file.name).toPath())
}
}
} else null
val otherResourceFiles =
if (otherFiles.isNotEmpty()) {
// Move the other resources files.
resourcesPath.kmpResolve("other").also { it.mkdirs() }.apply {
otherFiles.forEach { file ->
Files.move(file.toPath(), resolve(file.name).toPath())
}
}
} else {
null
}
return PatchesResult.PatchedResources(
resourcesApkFile,
@@ -190,7 +201,7 @@ class ResourcePatchContext internal constructor(
operator fun get(
path: String,
copy: Boolean = true,
) = apkFilesPath.resolve(path).apply {
) = apkFilesPath.kmpResolve(path).apply {
if (copy && !exists()) {
with(ExtFile(apkFile).directory) {
if (containsFile(path) || containsDir(path)) {

View File

@@ -1,11 +1,11 @@
package app.revanced.patcher.util
import com.android.tools.smali.dexlib2.mutable.MutableClassDef
import com.android.tools.smali.dexlib2.mutable.MutableClassDef.Companion.toMutable
import com.android.tools.smali.dexlib2.mutable.MutableField
import com.android.tools.smali.dexlib2.mutable.MutableField.Companion.toMutable
import com.android.tools.smali.dexlib2.mutable.MutableMethod
import com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableClassDef
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableClassDef.Companion.toMutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableField
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableField.Companion.toMutable
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.util.ClassMerger.Utils.asMutableClass
import app.revanced.patcher.util.ClassMerger.Utils.filterAny

View File

@@ -1,30 +1,30 @@
package app.revanced.patcher.util
import collections.merge
import app.revanced.collections.merge
import app.revanced.java.io.kmpBufferedWriter
import app.revanced.java.io.kmpInputStream
import com.google.common.base.Charsets
import org.w3c.dom.Document
import java.io.Closeable
import java.io.File
import java.io.InputStream
import java.io.bufferedWriter
import java.io.inputStream
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import kotlin.use
class Document internal constructor(
inputStream: InputStream,
) : Document by DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream), Closeable {
) : Document by DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(inputStream),
Closeable {
private var file: File? = null
init {
normalize()
}
internal constructor(file: File) : this(file.inputStream()) {
internal constructor(file: File) : this(file.kmpInputStream()) {
this.file = file
readerCount.merge(file, 1, Int::plus)
}
@@ -34,7 +34,7 @@ class Document internal constructor(
if (readerCount[it]!! > 1) {
throw IllegalStateException(
"Two or more instances are currently reading $it." +
"To be able to close this instance, no other instances may be reading $it at the same time.",
"To be able to close this instance, no other instances may be reading $it at the same time.",
)
} else {
readerCount.remove(it)
@@ -45,7 +45,7 @@ class Document internal constructor(
if (isAndroid) {
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-16")
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes")
val writer = it.bufferedWriter(charset = Charsets.UTF_8)
val writer = it.kmpBufferedWriter(charset = Charsets.UTF_8)
transformer.transform(DOMSource(this), StreamResult(writer))
writer.close()
} else {

View File

@@ -2,7 +2,7 @@
package app.revanced.patcher.util
import com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.patcher.extensions.instructionsOrNull
import app.revanced.patcher.patch.BytecodePatchContext
import com.android.tools.smali.dexlib2.iface.ClassDef
@@ -30,9 +30,10 @@ class MethodNavigator internal constructor(
context(_: BytecodePatchContext)
private val lastNavigatedMethodInstructions
get() = with(original()) {
instructionsOrNull ?: throw NavigateException("Method $this does not have an implementation.")
}
get() =
with(original()) {
instructionsOrNull ?: throw NavigateException("Method $this does not have an implementation.")
}
/**
* Navigate to the method at the specified index.
@@ -57,9 +58,16 @@ class MethodNavigator internal constructor(
* @param predicate The predicate to match.
*/
context(_: BytecodePatchContext)
fun to(index: Int = 0, predicate: (Instruction) -> Boolean): MethodNavigator {
lastNavigatedMethodReference = lastNavigatedMethodInstructions.asSequence()
.filter(predicate).asIterable().getMethodReferenceAt(index)
fun to(
index: Int = 0,
predicate: (Instruction) -> Boolean,
): MethodNavigator {
lastNavigatedMethodReference =
lastNavigatedMethodInstructions
.asSequence()
.filter(predicate)
.asIterable()
.getMethodReferenceAt(index)
return this
}
@@ -70,8 +78,9 @@ class MethodNavigator internal constructor(
* @param index The index of the method reference to get.
*/
private fun Iterable<Instruction>.getMethodReferenceAt(index: Int): MethodReference {
val instruction = elementAt(index) as? ReferenceInstruction
?: throw NavigateException("Instruction at index $index is not a method reference.")
val instruction =
elementAt(index) as? ReferenceInstruction
?: throw NavigateException("Instruction at index $index is not a method reference.")
return instruction.reference as MethodReference
}
@@ -82,17 +91,19 @@ class MethodNavigator internal constructor(
* @return The last navigated method mutably.
*/
context(context: BytecodePatchContext)
fun stop() = context.classDefs[lastNavigatedMethodReference.definingClass]!!
.firstMethodBySignature as MutableMethod
fun stop() =
context.classDefs[lastNavigatedMethodReference.definingClass]!!
.firstMethodBySignature as MutableMethod
/**
* Get the last navigated method mutably.
*
* @return The last navigated method mutably.
*/
operator fun getValue(context: BytecodePatchContext?, property: KProperty<*>) =
context(requireNotNull(context)) { stop() }
operator fun getValue(
context: BytecodePatchContext?,
property: KProperty<*>,
) = context(requireNotNull(context)) { stop() }
/**
* Get the last navigated method immutably.
@@ -106,14 +117,17 @@ class MethodNavigator internal constructor(
* Find the first [lastNavigatedMethodReference] in the class.
*/
private val ClassDef.firstMethodBySignature
get() = methods.first {
MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
}
get() =
methods.first {
MethodUtil.methodSignaturesMatch(it, lastNavigatedMethodReference)
}
/**
* An exception thrown when navigating fails.
*
* @param message The message of the exception.
*/
internal class NavigateException internal constructor(message: String) : Exception(message)
internal class NavigateException internal constructor(
message: String,
) : Exception(message)
}

View File

@@ -1,9 +0,0 @@
package com.android.tools.smali.dexlib2.iface.value
import com.android.tools.smali.dexlib2.base.value.BaseNullEncodedValue
class MutableNullEncodedValue : BaseNullEncodedValue(), MutableEncodedValue {
companion object {
fun ByteEncodedValue.toMutable(): MutableByteEncodedValue = MutableByteEncodedValue(this)
}
}

View File

@@ -1,17 +0,0 @@
package java.io
internal expect fun File.kmpResolve(child: String): File
internal fun File.resolve(child: String) = kmpResolve(child)
internal expect fun File.kmpDeleteRecursively(): Boolean
internal fun File.deleteRecursively() = kmpDeleteRecursively()
internal expect fun File.kmpInputStream(): InputStream
internal fun File.inputStream() = kmpInputStream()
internal expect fun File.kmpBufferedWriter(charset: java.nio.charset.Charset): BufferedWriter
internal fun File.bufferedWriter(charset: java.nio.charset.Charset) = kmpBufferedWriter(charset)

View File

@@ -0,0 +1,7 @@
package app.revanced.collections
internal actual fun <K, V> MutableMap<K, V>.kmpMerge(
key: K,
value: V,
remappingFunction: (oldValue: V, newValue: V) -> V,
) = MutableMap<K, V>::merge.call(key, value, remappingFunction) as Unit

View File

@@ -1,9 +1,12 @@
package java.io
package app.revanced.java.io
import java.io.File
import java.nio.charset.Charset
internal actual fun File.kmpResolve(child: String) = resolve(child)
internal actual fun File.kmpDeleteRecursively() = deleteRecursively()
internal actual fun File.kmpInputStream() = inputStream()
internal actual fun File.kmpBufferedWriter(charset: Charset) = bufferedWriter(charset)
internal actual fun File.kmpBufferedWriter(charset: Charset) = bufferedWriter(charset)

View File

@@ -24,9 +24,14 @@ actual fun loadPatches(
) = loadPatches(
patchesFiles = patchesFiles,
{ file ->
JarFile(file).entries().toList().filter { it.name.endsWith(".class") }
JarFile(file)
.entries()
.toList()
.filter { it.name.endsWith(".class") }
.map { it.name.substringBeforeLast('.').replace('/', '.') }
},
URLClassLoader(patchesFiles.map { it.toURI().toURL() }.toTypedArray()),
onFailedToLoad = onFailedToLoad
onFailedToLoad = onFailedToLoad,
)
actual inline val currentClassLoader get() = object {}::class.java.classLoader

View File

@@ -1,7 +0,0 @@
package collections
internal actual fun <K, V> MutableMap<K, V>.kmpMerge(
key: K,
value: V,
remappingFunction: (oldValue: V, newValue: V) -> V
) = merge(key, value, remappingFunction)

View File

@@ -21,42 +21,44 @@ class MatchingTest : PatcherTestBase() {
@Test
fun `finds via builder api`() {
fun firstMethodComposite(fail: Boolean = false) = firstMethodComposite {
name("method")
definingClass("class")
fun firstMethodComposite(fail: Boolean = false) =
firstMethodComposite {
name("method")
definingClass("class")
if (fail) returnType("doesnt exist")
if (fail) returnType("doesnt exist")
instructions(
at(0, Opcode.CONST_STRING()),
`is`<TwoRegisterInstruction>(),
noneOf(registers()),
string("test", String::contains),
after(1..3, allOf(Opcode.INVOKE_VIRTUAL(), registers(1, 0))),
allOf(),
type("PrintStream;", String::endsWith)
)
}
instructions(
at(0, Opcode.CONST_STRING()),
`is`<TwoRegisterInstruction>(),
noneOf(registers()),
string("test", String::contains),
after(1..3, allOf(Opcode.INVOKE_VIRTUAL(), registers(1, 0))),
allOf(),
type("PrintStream;", String::endsWith),
)
}
with(bytecodePatchContext) {
val match = firstMethodComposite()
assertNotNull(
match.methodOrNull,
"Expected to find a method"
"Expected to find a method",
)
assertEquals(
4, match.indices[3],
"Expected to find the string instruction at index 5"
4,
match.indices[3],
"Expected to find the string instruction at index 4",
)
assertNull(
firstMethodComposite(fail = true).immutableMethodOrNull,
"Expected to not find a method"
"Expected to not find a method",
)
assertNotNull(
firstMethodComposite().match(classDefs.first()).methodOrNull,
"Expected to find a method matching in a specific class"
"Expected to find a method matching in a specific class",
)
}
}
@@ -65,16 +67,17 @@ class MatchingTest : PatcherTestBase() {
fun `finds via declarative api`() {
bytecodePatch {
apply {
val method = firstMethodDeclarativelyOrNull {
anyOf {
predicate { name == "method" }
add { false }
val method =
firstMethodDeclarativelyOrNull {
anyOf {
predicate { name == "method" }
add { false }
}
allOf {
predicate { returnType == "V" }
}
predicate { definingClass == "class" }
}
allOf {
predicate { returnType == "V" }
}
predicate { definingClass == "class" }
}
assertNotNull(method) { "Expected to find a method" }
}
}()
@@ -99,7 +102,7 @@ class MatchingTest : PatcherTestBase() {
}
assertFalse(
matcher(iterable),
"Should not match at any other index than first"
"Should not match at any other index than first",
)
matcher.clear()
@@ -107,7 +110,7 @@ class MatchingTest : PatcherTestBase() {
assertEquals(
listOf(0),
matcher.indices,
"Should match at first index."
"Should match at first index.",
)
matcher.clear()
@@ -119,7 +122,7 @@ class MatchingTest : PatcherTestBase() {
assertEquals(
listOf(1),
matcher.indices,
"Should find the index correctly."
"Should find the index correctly.",
)
matcher.clear()
@@ -131,7 +134,7 @@ class MatchingTest : PatcherTestBase() {
assertEquals(
listOf(0, 1, 3),
matcher.indices,
"Should match 1, 2 and 4 at indices 0, 1 and 3."
"Should match 1, 2 and 4 at indices 0, 1 and 3.",
)
matcher.clear()
@@ -141,7 +144,7 @@ class MatchingTest : PatcherTestBase() {
assertEquals(
listOf(0),
matcher.indices,
"Should match index 0 after nothing"
"Should match index 0 after nothing",
)
matcher.clear()
@@ -150,7 +153,7 @@ class MatchingTest : PatcherTestBase() {
}
assertFalse(
matcher(iterable),
"Should not match, because 1 is out of range"
"Should not match, because 1 is out of range",
)
matcher.clear()
@@ -159,7 +162,7 @@ class MatchingTest : PatcherTestBase() {
}
assertFalse(
matcher(iterable),
"Should not match, because 2 is at index 1"
"Should not match, because 2 is at index 1",
)
matcher.clear()
@@ -172,13 +175,18 @@ class MatchingTest : PatcherTestBase() {
assertEquals(
listOf(0, 3, 7, 8),
matcher.indices,
"Should match indices correctly."
"Should match indices correctly.",
)
}
@Test
fun `unordered matching works correctly`() {
val list = bytecodePatchContext.classDefs.first().methods.first().instructions
val list =
bytecodePatchContext.classDefs
.first()
.methods
.first()
.instructions
val matcher = indexedMatcher<Instruction>()
matcher.apply {
@@ -187,13 +195,13 @@ class MatchingTest : PatcherTestBase() {
afterAtLeast(1, Opcode.RETURN_OBJECT()),
string(),
Opcode.INVOKE_VIRTUAL(),
)
),
)
}(list)
assertEquals(
listOf(4, 5, 6),
matcher.indices,
"Should match because after(1) luckily only matches after the string at index 4."
"Should match because after(1) luckily only matches after the string at index 4.",
)
matcher.clear()
@@ -203,23 +211,25 @@ class MatchingTest : PatcherTestBase() {
string("test", String::contains),
string("Hello", String::contains),
afterAtLeast(1, Opcode.RETURN_OBJECT()),
)
),
)
}(list)
assertEquals(
listOf(0, 4, 5),
matcher.indices,
"Should first match indices 4 due to the string, then after due to step 1, then the invoke."
"Should first match indices 4 due to the string, then after due to step 1, then the invoke.",
)
assertFalse(
indexedMatcher<Int>(
items = unorderedAllOf(
{ _, _, _ -> this == 1 },
{ _, _, _ -> this == -1 },
after(2) { this == -2 }
))(listOf(1, -1, 1, 2, -2)),
"Should not match because because 1 is matched at index 0, too early for after(2)."
items =
unorderedAllOf(
{ _, _, _ -> this == 1 },
{ _, _, _ -> this == -1 },
after(2) { this == -2 },
),
)(listOf(1, -1, 1, 2, -2)),
"Should not match because because 1 is matched at index 0, too early for after(2).",
)
}
}

View File

@@ -1,5 +1,7 @@
package app.revanced.patcher.extensions
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
@@ -7,24 +9,23 @@ import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21s
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.mutable.MutableMethod
import com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import org.junit.jupiter.api.BeforeEach
import kotlin.test.Test
import kotlin.test.assertEquals
internal class MethodExtensionsTest {
private val testInstructions = (0..9).map { i -> TestInstruction(i) }
private var method = ImmutableMethod(
"TestClass;",
"testMethod",
null,
"V",
AccessFlags.PUBLIC.value,
null,
null,
MutableMethodImplementation(16)
).toMutable()
private var method =
ImmutableMethod(
"TestClass;",
"testMethod",
null,
"V",
AccessFlags.PUBLIC.value,
null,
null,
MutableMethodImplementation(16),
).toMutable()
@BeforeEach
fun setup() {
@@ -249,5 +250,7 @@ internal class MethodExtensionsTest {
// endregion
private class TestInstruction(register: Int) : BuilderInstruction21s(Opcode.CONST_16, register, 0)
private class TestInstruction(
register: Int,
) : BuilderInstruction21s(Opcode.CONST_16, register, 0)
}

View File

@@ -1,29 +1,29 @@
package app.revanced.patcher.util
import app.revanced.com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import app.revanced.patcher.extensions.*
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.builder.BuilderOffsetInstruction
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21t
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.mutable.MutableMethod.Companion.toMutable
import org.junit.jupiter.api.BeforeEach
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
internal class SmaliTest {
val method = ImmutableMethod(
"Ldummy;",
"name",
emptyList(), // parameters
"V",
AccessFlags.PUBLIC.value,
null,
null,
MutableMethodImplementation(1),
).toMutable()
val method =
ImmutableMethod(
"Ldummy;",
"name",
emptyList(), // parameters
"V",
AccessFlags.PUBLIC.value,
null,
null,
MutableMethodImplementation(1),
).toMutable()
@BeforeEach
fun setup() {
@@ -41,7 +41,10 @@ internal class SmaliTest {
""",
)
val targetLocationIndex = method.getInstruction<BuilderOffsetInstruction>(1).target.location.index
val targetLocationIndex =
method
.getInstruction<BuilderOffsetInstruction>(1)
.target.location.index
assertEquals(0, targetLocationIndex, "Label should point to index 0")
}
@@ -73,4 +76,4 @@ internal class SmaliTest {
assertTrue(instruction.target.isPlaced, "Label should be placed")
assertEquals(labelIndex, instruction.target.location.index)
}
}
}