Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Instagram): add Download patch #222

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
12 changes: 9 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import org.gradle.kotlin.dsl.support.listFilesOrdered

plugins {
kotlin("jvm") version "1.9.23"
alias(libs.plugins.kotlin)
`maven-publish`
}

Expand All @@ -11,7 +11,13 @@ repositories {
mavenCentral()
mavenLocal()
google()
maven { url = uri("https://jitpack.io") }
maven {
url = uri("https://maven.pkg.github.com/revanced/registry")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")
password = project.findProperty("gpr.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
}

dependencies {
Expand Down Expand Up @@ -59,7 +65,7 @@ tasks {

exec {
workingDir = workingDirectory
commandLine = listOf(d8, artifacts)
commandLine = listOf(d8, "--release", artifacts)
}

exec {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 1.27.0-dev.8
version = 1.27.0-dev.8
8 changes: 7 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
[versions]
revanced-patcher = "19.2.0"
revanced-patcher = "19.3.1"
smali = "3.0.5"
guava = "33.1.0-jre"
gson = "2.10.1"
binary-compatibility-validator = "0.14.0"
kotlin = "1.9.22"

[libraries]
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
smali = { module = "com.android.tools.smali:smali", version.ref = "smali" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }

[plugins]
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
Original file line number Diff line number Diff line change
@@ -1,49 +1,54 @@
package app.revanced.patches.shared.misc.integrations

import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchException
import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch.IntegrationsFingerprint.IRegisterResolver
import app.revanced.util.resultOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import crimera.patches.twitter.misc.integrations.fingerprints.ReVancedUtilsPatchesVersionFingerprint
import java.util.jar.JarFile

abstract class BaseIntegrationsPatch(
private val hooks: Set<IntegrationsFingerprint>
private val hooks: Set<IntegrationsFingerprint>,
) : BytecodePatch(hooks + setOf(ReVancedUtilsPatchesVersionFingerprint)) {

@Deprecated(
"Use the constructor without the integrationsDescriptor parameter",
ReplaceWith("AbstractIntegrationsPatch(hooks)")
ReplaceWith("BaseIntegrationsPatch(hooks)"),
)
@Suppress("UNUSED_PARAMETER")
constructor(
integrationsDescriptor: String,
hooks: Set<IntegrationsFingerprint>
hooks: Set<IntegrationsFingerprint>,
) : this(hooks)

override fun execute(context: BytecodeContext) {
if (context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR) == null) throw PatchException(
"Integrations have not been merged yet. This patch can not succeed without merging the integrations."
)
if (context.findClass(INTEGRATIONS_CLASS_DESCRIPTOR) == null) {
throw PatchException(
"Integrations have not been merged yet. This patch can not succeed without merging the integrations.",
)
}

hooks.forEach { hook ->
hook.invoke(INTEGRATIONS_CLASS_DESCRIPTOR)
}

ReVancedUtilsPatchesVersionFingerprint.result?.mutableMethod?.apply {
// Modify Utils method to include the patches release version version.
ReVancedUtilsPatchesVersionFingerprint.resultOrThrow().mutableMethod.apply {
val manifestValue = getPatchesManifestEntry("Version")

addInstructions(
0,
"""
const-string v0, "$manifestValue"
return-object v0
"""
const-string v0, "$manifestValue"
return-object v0
""",
)
}
}
Expand Down Expand Up @@ -88,30 +93,57 @@ abstract class BaseIntegrationsPatch(
opcodes: Iterable<Opcode?>? = null,
strings: Iterable<String>? = null,
customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null,
private val contextRegisterResolver: (Method) -> Int = object : IRegisterResolver {}
private val insertIndexResolver: ((Method) -> Int) = object : IHookInsertIndexResolver {},
private val contextRegisterResolver: (Method) -> Int = object : IRegisterResolver {},
) : MethodFingerprint(
returnType,
accessFlags,
parameters,
opcodes,
strings,
customFingerprint
customFingerprint,
) {
@Deprecated(
"Previous constructor that is missing the insert index." +
"Here only for binary compatibility, " +
"and this can be removed after the next major version update.",
)
constructor(
returnType: String? = null,
accessFlags: Int? = null,
parameters: Iterable<String>? = null,
opcodes: Iterable<Opcode?>? = null,
strings: Iterable<String>? = null,
customFingerprint: ((methodDef: Method, classDef: ClassDef) -> Boolean)? = null,
contextRegisterResolver: (Method) -> Int = object : IRegisterResolver {},
) : this(
returnType,
accessFlags,
parameters,
opcodes,
strings,
customFingerprint,
object : IHookInsertIndexResolver {},
contextRegisterResolver,
)

fun invoke(integrationsDescriptor: String) {
result?.mutableMethod?.let { method ->
val insertIndex = insertIndexResolver(method)
val contextRegister = contextRegisterResolver(method)

method.addInstructions(
0,
"""
sput-object v$contextRegister,$integrationsDescriptor->context:Landroid/content/Context;
invoke-static {}, $integrationsDescriptor->load()V

""".trimIndent()
method.addInstruction(
insertIndex,
"invoke-static/range { v$contextRegister .. v$contextRegister }, " +
"$integrationsDescriptor->setContext(Landroid/content/Context;)V",
)
} ?: throw PatchException("Could not find hook target fingerprint.")
}

interface IHookInsertIndexResolver : (Method) -> Int {
override operator fun invoke(method: Method) = 0
}

interface IRegisterResolver : (Method) -> Int {
override operator fun invoke(method: Method) = method.implementation!!.registerCount - 1
}
Expand Down
142 changes: 142 additions & 0 deletions src/main/kotlin/app/revanced/util/BytecodeUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package app.revanced.util

import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.fingerprint.MethodFingerprint
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
import com.android.tools.smali.dexlib2.iface.reference.Reference
import com.android.tools.smali.dexlib2.util.MethodUtil

fun MethodFingerprint.resultOrThrow() = result ?: throw exception

/**
* The [PatchException] of failing to resolve a [MethodFingerprint].
*
* @return The [PatchException].
*/
val MethodFingerprint.exception
get() = PatchException("Failed to resolve ${this.javaClass.simpleName}")

/**
* Find the [MutableMethod] from a given [Method] in a [MutableClass].
*
* @param method The [Method] to find.
* @return The [MutableMethod].
*/
fun MutableClass.findMutableMethodOf(method: Method) = this.methods.first {
MethodUtil.methodSignaturesMatch(it, method)
}

/**
* Apply a transform to all methods of the class.
*
* @param transform The transformation function. Accepts a [MutableMethod] and returns a transformed [MutableMethod].
*/
fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) {
val transformedMethods = methods.map { it.transform() }
methods.clear()
methods.addAll(transformedMethods)
}

/**
* Inject a call to a method that hides a view.
*
* @param insertIndex The index to insert the call at.
* @param viewRegister The register of the view to hide.
* @param classDescriptor The descriptor of the class that contains the method.
* @param targetMethod The name of the method to call.
*/
fun MutableMethod.injectHideViewCall(
insertIndex: Int,
viewRegister: Int,
classDescriptor: String,
targetMethod: String,
) = addInstruction(
insertIndex,
"invoke-static { v$viewRegister }, $classDescriptor->$targetMethod(Landroid/view/View;)V",
)

/**
* Find the index of the first wide literal instruction with the given value.
*
* @return the first literal instruction with the value, or -1 if not found.
*/
fun Method.indexOfFirstWideLiteralInstructionValue(literal: Long) = implementation?.let {
it.instructions.indexOfFirst { instruction ->
(instruction as? WideLiteralInstruction)?.wideLiteral == literal
}
} ?: -1

/**
* Check if the method contains a literal with the given value.
*
* @return if the method contains a literal with the given value.
*/
fun Method.containsWideLiteralInstructionValue(literal: Long) =
indexOfFirstWideLiteralInstructionValue(literal) >= 0

/**
* Traverse the class hierarchy starting from the given root class.
*
* @param targetClass the class to start traversing the class hierarchy from.
* @param callback function that is called for every class in the hierarchy.
*/
fun BytecodeContext.traverseClassHierarchy(targetClass: MutableClass, callback: MutableClass.() -> Unit) {
callback(targetClass)
this.findClass(targetClass.superclass ?: return)?.mutableClass?.let {
traverseClassHierarchy(it, callback)
}
}

/**
* Get the [Reference] of an [Instruction] as [T].
*
* @param T The type of [Reference] to cast to.
* @return The [Reference] as [T] or null
* if the [Instruction] is not a [ReferenceInstruction] or the [Reference] is not of type [T].
* @see ReferenceInstruction
*/
inline fun <reified T : Reference> Instruction.getReference() = (this as? ReferenceInstruction)?.reference as? T

/**
* Get the index of the first [Instruction] that matches the predicate.
*
* @param predicate The predicate to match.
* @return The index of the first [Instruction] that matches the predicate.
*/
fun Method.indexOfFirstInstruction(predicate: Instruction.() -> Boolean) =
this.implementation!!.instructions.indexOfFirst(predicate)

/**
* Return the resolved methods of [MethodFingerprint]s early.
*/
fun List<MethodFingerprint>.returnEarly(bool: Boolean = false) {
val const = if (bool) "0x1" else "0x0"
this.forEach { fingerprint ->
fingerprint.result?.let { result ->
val stringInstructions = when (result.method.returnType.first()) {
'L' ->
"""
const/4 v0, $const
return-object v0
"""
'V' -> "return-void"
'I', 'Z' ->
"""
const/4 v0, $const
return v0
"""
else -> throw Exception("This case should never happen.")
}

result.mutableMethod.addInstructions(0, stringInstructions)
} ?: throw fingerprint.exception
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import org.w3c.dom.Element


@Patch(
name = "Enable app downgrading",
description = "Sets app version to a default value making installation of different versions possible",
use = false
name = "Enable app downgrading",
description =
"Sets app version to a default value making installation of different versions possible",
use = false
)
object AppDowngradingPatch: ResourcePatch() {
object AppDowngradingPatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use {
val manifestElement = it.file.getElementsByTagName("manifest").item(0) as Element
Expand Down
Loading