mirror of
https://github.com/ReVanced/revanced-patcher.git
synced 2026-01-29 22:21:03 +00:00
fix build system issues
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
@@ -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) {
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -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) {
|
||||
@@ -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) {
|
||||
@@ -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) {
|
||||
@@ -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) {
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
@@ -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() }
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
14
patcher/src/commonMain/kotlin/app/revanced/java/io/File.kt
Normal file
14
patcher/src/commonMain/kotlin/app/revanced/java/io/File.kt
Normal 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
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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).",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user