From b42ae27ce66ebad9e9cfc5b70fc121df5bad7567 Mon Sep 17 00:00:00 2001 From: xehpuk Date: Sun, 4 Jan 2026 03:36:08 +0100 Subject: [PATCH] feat(Strava): Add `Overwrite media upload parameters` patch (#6410) --- patches/api/patches.api | 6 +- .../strava/mediaupload/Fingerprints.kt | 21 + .../OverwriteMediaUploadParametersPatch.kt | 48 ++ .../password/EnablePasswordLoginPatch.kt | 9 +- .../patches/strava/password/Fingerprints.kt | 3 - .../strava/subscription/Fingerprints.kt | 4 +- .../subscription/UnlockSubscriptionPatch.kt | 7 +- .../kotlin/app/revanced/util/BytecodeUtils.kt | 519 +++++++++++------- .../main/kotlin/app/revanced/util/Utils.kt | 19 +- 9 files changed, 407 insertions(+), 229 deletions(-) create mode 100644 patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/OverwriteMediaUploadParametersPatch.kt diff --git a/patches/api/patches.api b/patches/api/patches.api index b2d0eaa09..63a56070a 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1188,6 +1188,10 @@ public final class app/revanced/patches/stocard/layout/HideStoryBubblesPatchKt { public static final fun getHideStoryBubblesPatch ()Lapp/revanced/patcher/patch/ResourcePatch; } +public final class app/revanced/patches/strava/mediaupload/OverwriteMediaUploadParametersPatchKt { + public static final fun getOverwriteMediaUploadParametersPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/strava/password/EnablePasswordLoginPatchKt { public static final fun getEnablePasswordLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -1971,6 +1975,7 @@ public final class app/revanced/util/BytecodeUtilsKt { public static final fun indexOfFirstResourceIdOrThrow (Lcom/android/tools/smali/dexlib2/iface/Method;Ljava/lang/String;)I public static final fun injectHideViewCall (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;IILjava/lang/String;Ljava/lang/String;)V public static final fun literal (Lapp/revanced/patcher/FingerprintBuilder;Lkotlin/jvm/functions/Function0;)V + public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V @@ -1980,7 +1985,6 @@ public final class app/revanced/util/BytecodeUtilsKt { public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Ljava/lang/String;)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;S)V public static final fun returnEarly (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;Z)V - public static synthetic fun returnEarly$default (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;ZILjava/lang/Object;)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;B)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;C)V public static final fun returnLate (Lapp/revanced/patcher/util/proxy/mutableTypes/MutableMethod;D)V diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/Fingerprints.kt new file mode 100644 index 000000000..7901d6cc5 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/Fingerprints.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.strava.mediaupload + +import app.revanced.patcher.fingerprint + +internal val getCompressionQualityFingerprint = fingerprint { + custom { method, _ -> + method.name == "getCompressionQuality" + } +} + +internal val getMaxDurationFingerprint = fingerprint { + custom { method, _ -> + method.name == "getMaxDuration" + } +} + +internal val getMaxSizeFingerprint = fingerprint { + custom { method, _ -> + method.name == "getMaxSize" + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/OverwriteMediaUploadParametersPatch.kt b/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/OverwriteMediaUploadParametersPatch.kt new file mode 100644 index 000000000..182306c6d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/strava/mediaupload/OverwriteMediaUploadParametersPatch.kt @@ -0,0 +1,48 @@ +package app.revanced.patches.strava.mediaupload + +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patcher.patch.intOption +import app.revanced.patcher.patch.longOption +import app.revanced.util.returnEarly + +@Suppress("unused") +val overwriteMediaUploadParametersPatch = bytecodePatch( + name = "Overwrite media upload parameters", + description = "Overwrites the compression, resize and trim media (images and videos) parameters returned by Strava's server before upload.", +) { + compatibleWith("com.strava") + + val compressionQuality by intOption( + key = "compressionQuality", + title = "Compression quality (percent)", + description = "This is used as the JPEG quality setting (≤ 100).", + ) { it == null || it in 1..100 } + + val maxDuration by longOption( + key = "maxDuration", + title = "Max duration (seconds)", + description = "The maximum length (≤ ${60 * 60}) of a video before it gets trimmed.", + ) { it == null || it in 1..60 * 60 } + + val maxSize by intOption( + key = "maxSize", + title = "Max size (pixels)", + description = "The image gets resized so that the smaller dimension (width/height) does not exceed this value (≤ 10000).", + ) { it == null || it in 1..10000 } + + execute { + val mediaUploadParametersClass = classes.single { it.endsWith("/MediaUploadParameters;") } + + compressionQuality?.let { compressionQuality -> + getCompressionQualityFingerprint.match(mediaUploadParametersClass).method.returnEarly(compressionQuality / 100f) + } + + maxDuration?.let { maxDuration -> + getMaxDurationFingerprint.match(mediaUploadParametersClass).method.returnEarly(maxDuration) + } + + maxSize?.let { + getMaxSizeFingerprint.match(mediaUploadParametersClass).method.returnEarly(it) + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/password/EnablePasswordLoginPatch.kt b/patches/src/main/kotlin/app/revanced/patches/strava/password/EnablePasswordLoginPatch.kt index 1d6a62569..6b7e74235 100644 --- a/patches/src/main/kotlin/app/revanced/patches/strava/password/EnablePasswordLoginPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/strava/password/EnablePasswordLoginPatch.kt @@ -1,8 +1,8 @@ package app.revanced.patches.strava.password import app.revanced.patcher.Fingerprint -import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.returnEarly @Suppress("unused") val enablePasswordLoginPatch = bytecodePatch( @@ -12,10 +12,9 @@ val enablePasswordLoginPatch = bytecodePatch( compatibleWith("com.strava") execute { - fun Fingerprint.loadTrueInsteadOfField() = - method.replaceInstruction(patternMatch!!.startIndex, "const/4 v0, 0x1") + fun Fingerprint.returnTrue() = method.returnEarly(true) - logInGetUsePasswordFingerprint.loadTrueInsteadOfField() - emailChangeGetUsePasswordFingerprint.loadTrueInsteadOfField() + logInGetUsePasswordFingerprint.returnTrue() + emailChangeGetUsePasswordFingerprint.returnTrue() } } diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/password/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/strava/password/Fingerprints.kt index f5ce86f18..94c88490a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/strava/password/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/strava/password/Fingerprints.kt @@ -1,17 +1,14 @@ package app.revanced.patches.strava.password import app.revanced.patcher.fingerprint -import com.android.tools.smali.dexlib2.Opcode internal val logInGetUsePasswordFingerprint = fingerprint { - opcodes(Opcode.IGET_BOOLEAN) custom { method, classDef -> method.name == "getUsePassword" && classDef.endsWith("/RequestOtpLogInNetworkResponse;") } } internal val emailChangeGetUsePasswordFingerprint = fingerprint { - opcodes(Opcode.IGET_BOOLEAN) custom { method, classDef -> method.name == "getUsePassword" && classDef.endsWith("/RequestEmailChangeWithOtpOrPasswordResponse;") } diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/subscription/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/strava/subscription/Fingerprints.kt index 0458f45d3..45583ce4e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/strava/subscription/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/strava/subscription/Fingerprints.kt @@ -1,11 +1,9 @@ package app.revanced.patches.strava.subscription import app.revanced.patcher.fingerprint -import com.android.tools.smali.dexlib2.Opcode internal val getSubscribedFingerprint = fingerprint { - opcodes(Opcode.IGET_BOOLEAN) custom { method, classDef -> - classDef.endsWith("/SubscriptionDetailResponse;") && method.name == "getSubscribed" + method.name == "getSubscribed" && classDef.endsWith("/SubscriptionDetailResponse;") } } diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/subscription/UnlockSubscriptionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/strava/subscription/UnlockSubscriptionPatch.kt index e59660472..e9fbc49a4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/strava/subscription/UnlockSubscriptionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/strava/subscription/UnlockSubscriptionPatch.kt @@ -1,7 +1,7 @@ package app.revanced.patches.strava.subscription -import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.returnEarly @Suppress("unused") val unlockSubscriptionPatch = bytecodePatch( @@ -11,9 +11,6 @@ val unlockSubscriptionPatch = bytecodePatch( compatibleWith("com.strava") execute { - getSubscribedFingerprint.method.replaceInstruction( - getSubscribedFingerprint.patternMatch!!.startIndex, - "const/4 v0, 0x1", - ) + getSubscribedFingerprint.method.returnEarly(true) } } diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 993fa820b..f5a39a996 100644 --- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -23,22 +23,19 @@ import app.revanced.util.InstructionUtils.Companion.writeOpcodes import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode.* +import com.android.tools.smali.dexlib2.analysis.reflection.util.ReflectionUtils +import com.android.tools.smali.dexlib2.formatter.DexFormatter import com.android.tools.smali.dexlib2.iface.Method -import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.Instruction -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction -import com.android.tools.smali.dexlib2.iface.instruction.RegisterRangeInstruction -import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction -import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction +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.iface.reference.StringReference +import com.android.tools.smali.dexlib2.iface.value.* import com.android.tools.smali.dexlib2.immutable.ImmutableField +import com.android.tools.smali.dexlib2.immutable.value.* import com.android.tools.smali.dexlib2.util.MethodUtil -import java.util.EnumSet +import java.util.* /** * Starting from and including the instruction at index [startIndex], @@ -180,7 +177,7 @@ internal val Instruction.isReturnInstruction: Boolean * * @param fieldName The name of the field to find. Partial matches are allowed. */ -private fun Method.findInstructionIndexFromToString(fieldName: String) : Int { +private fun Method.findInstructionIndexFromToString(fieldName: String): Int { val stringIndex = indexOfFirstInstruction { val reference = getReference() reference?.string?.contains(fieldName) == true @@ -233,7 +230,7 @@ private fun Method.findInstructionIndexFromToString(fieldName: String) : Int { * @param fieldName The name of the field to find. Partial matches are allowed. */ context(BytecodePatchContext) -internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod { +internal fun Method.findMethodFromToString(fieldName: String): MutableMethod { val methodUsageIndex = findInstructionIndexFromToString(fieldName) return navigate(this).to(methodUsageIndex).stop() } @@ -243,7 +240,7 @@ internal fun Method.findMethodFromToString(fieldName: String) : MutableMethod { * * @param fieldName The name of the field to find. Partial matches are allowed. */ -internal fun Method.findFieldFromToString(fieldName: String) : FieldReference { +internal fun Method.findFieldFromToString(fieldName: String): FieldReference { val methodUsageIndex = findInstructionIndexFromToString(fieldName) return getInstruction(methodUsageIndex).getReference()!! } @@ -838,23 +835,59 @@ fun BytecodePatchContext.forEachLiteralValueInstruction( } -private const val RETURN_TYPE_MISMATCH = "Mismatch between override type and Method return type" +private fun MutableMethod.checkReturnType(expectedTypes: Iterable>) { + val returnTypeJava = ReflectionUtils.dexToJavaName(returnType) + check(expectedTypes.any { returnTypeJava == it.name }) { + "Actual return type $returnTypeJava is not contained in expected types: $expectedTypes" + } +} /** - * Overrides the first instruction of a method with a constant `Boolean` return value. + * Overrides the first instruction of a method with returning the default value for the type (or `void`). * None of the method code will ever execute. * - * For methods that return an object or any array type, calling this method with `false` - * will force the method to return a `null` value. - * * @see returnLate */ -fun MutableMethod.returnEarly(value: Boolean = false) { - val returnType = returnType.first() - check(returnType == 'Z' || (!value && (returnType == 'V' || returnType == 'L' || returnType != '['))) { - RETURN_TYPE_MISMATCH +fun MutableMethod.returnEarly() { + val value = when (returnType) { + "V" -> null + "Z" -> ImmutableBooleanEncodedValue.FALSE_VALUE + "B" -> ImmutableByteEncodedValue(0) + "S" -> ImmutableShortEncodedValue(0) + "C" -> ImmutableCharEncodedValue(Char.MIN_VALUE) + "I" -> ImmutableIntEncodedValue(0) + "F" -> ImmutableFloatEncodedValue(0f) + "J" -> ImmutableLongEncodedValue(0) + "D" -> ImmutableDoubleEncodedValue(0.0) + else -> ImmutableNullEncodedValue.INSTANCE } - overrideReturnValue(value.toHexString(), false) + overrideReturnValue(value, false) +} + +private fun MutableMethod.returnString(value: String, late: Boolean) { + checkReturnType(String::class.java.allAssignableTypes()) + overrideReturnValue(ImmutableStringEncodedValue(value), late) +} + +/** + * Overrides the first instruction of a method with a constant `String` return value. + * None of the method code will ever execute. + * + * @see returnLate + */ +fun MutableMethod.returnEarly(value: String) = returnString(value, false) + +/** + * Overrides all return statements with a constant `String` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: String) = returnString(value, true) + +private fun MutableMethod.returnByte(value: Byte, late: Boolean) { + checkReturnType(Byte::class.javaObjectType.allAssignableTypes() + Byte::class.javaPrimitiveType!!) + overrideReturnValue(ImmutableByteEncodedValue(value), late) } /** @@ -863,9 +896,40 @@ fun MutableMethod.returnEarly(value: Boolean = false) { * * @see returnLate */ -fun MutableMethod.returnEarly(value: Byte) { - check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), false) +fun MutableMethod.returnEarly(value: Byte) = returnByte(value, false) + +/** + * Overrides all return statements with a constant `Byte` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Byte) = returnByte(value, true) + +private fun MutableMethod.returnBoolean(value: Boolean, late: Boolean) { + checkReturnType(Boolean::class.javaObjectType.allAssignableTypes() + Boolean::class.javaPrimitiveType!!) + overrideReturnValue(ImmutableBooleanEncodedValue.forBoolean(value), late) +} + +/** + * Overrides the first instruction of a method with a constant `Boolean` return value. + * None of the method code will ever execute. + * + * @see returnLate + */ +fun MutableMethod.returnEarly(value: Boolean) = returnBoolean(value, false) + +/** + * Overrides all return statements with a constant `Boolean` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Boolean) = returnBoolean(value, true) + +private fun MutableMethod.returnShort(value: Short, late: Boolean) { + checkReturnType(Short::class.javaObjectType.allAssignableTypes() + Short::class.javaPrimitiveType!!) + overrideReturnValue(ImmutableShortEncodedValue(value), late) } /** @@ -874,9 +938,19 @@ fun MutableMethod.returnEarly(value: Byte) { * * @see returnLate */ -fun MutableMethod.returnEarly(value: Short) { - check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), false) +fun MutableMethod.returnEarly(value: Short) = returnShort(value, false) + +/** + * Overrides all return statements with a constant `Short` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Short) = returnShort(value, true) + +private fun MutableMethod.returnChar(value: Char, late: Boolean) { + checkReturnType(Char::class.javaObjectType.allAssignableTypes() + Char::class.javaPrimitiveType!!) + overrideReturnValue(ImmutableCharEncodedValue(value), late) } /** @@ -885,9 +959,19 @@ fun MutableMethod.returnEarly(value: Short) { * * @see returnLate */ -fun MutableMethod.returnEarly(value: Char) { - check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.code.toString(), false) +fun MutableMethod.returnEarly(value: Char) = returnChar(value, false) + +/** + * Overrides all return statements with a constant `Char` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Char) = returnChar(value, true) + +private fun MutableMethod.returnInt(value: Int, late: Boolean) { + checkReturnType(Int::class.javaObjectType.allAssignableTypes() + Int::class.javaPrimitiveType!!) + overrideReturnValue(ImmutableIntEncodedValue(value), late) } /** @@ -896,20 +980,19 @@ fun MutableMethod.returnEarly(value: Char) { * * @see returnLate */ -fun MutableMethod.returnEarly(value: Int) { - check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), false) -} +fun MutableMethod.returnEarly(value: Int) = returnInt(value, false) /** - * Overrides the first instruction of a method with a constant `Long` return value. - * None of the method code will ever execute. + * Overrides all return statements with a constant `Int` value. + * All method code is executed the same as unpatched. * - * @see returnLate + * @see returnEarly */ -fun MutableMethod.returnEarly(value: Long) { - check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), false) +fun MutableMethod.returnLate(value: Int) = returnInt(value, true) + +private fun MutableMethod.returnFloat(value: Float, late: Boolean) { + checkReturnType(Float::class.javaObjectType.allAssignableTypes() + Float::class.javaPrimitiveType!!) + overrideReturnValue(ImmutableFloatEncodedValue(value), late) } /** @@ -918,9 +1001,40 @@ fun MutableMethod.returnEarly(value: Long) { * * @see returnLate */ -fun MutableMethod.returnEarly(value: Float) { - check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), false) +fun MutableMethod.returnEarly(value: Float) = returnFloat(value, false) + +/** + * Overrides all return statements with a constant `Float` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Float) = returnFloat(value, true) + +private fun MutableMethod.returnLong(value: Long, late: Boolean) { + checkReturnType(Long::class.javaObjectType.allAssignableTypes() + Long::class.javaPrimitiveType!!) + overrideReturnValue(ImmutableLongEncodedValue(value), late) +} + +/** + * Overrides the first instruction of a method with a constant `Long` return value. + * None of the method code will ever execute. + * + * @see returnLate + */ +fun MutableMethod.returnEarly(value: Long) = returnLong(value, false) + +/** + * Overrides all return statements with a constant `Long` value. + * All method code is executed the same as unpatched. + * + * @see returnEarly + */ +fun MutableMethod.returnLate(value: Long) = returnLong(value, true) + +private fun MutableMethod.returnDouble(value: Double, late: Boolean) { + checkReturnType(Double::class.javaObjectType.allAssignableTypes() + Double::class.javaPrimitiveType!!) + overrideReturnValue(ImmutableDoubleEncodedValue(value), late) } /** @@ -929,113 +1043,7 @@ fun MutableMethod.returnEarly(value: Float) { * * @see returnLate */ -fun MutableMethod.returnEarly(value: Double) { - check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), false) -} - -/** - * Overrides the first instruction of a method with a constant String return value. - * None of the method code will ever execute. - * - * Target method must have return type - * Ljava/lang/String; or Ljava/lang/CharSequence; - * - * @see returnLate - */ -fun MutableMethod.returnEarly(value: String) { - check(returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;") { - RETURN_TYPE_MISMATCH - } - overrideReturnValue(value, false) -} - -/** - * Overrides all return statements with a constant `Boolean` value. - * All method code is executed the same as unpatched. - * - * For methods that return an object or any array type, calling this method with `false` - * will force the method to return a `null` value. - * - * @see returnEarly - */ -fun MutableMethod.returnLate(value: Boolean) { - val returnType = returnType.first() - if (returnType == 'V') { - error("Cannot return late for Method of void type") - } - check(returnType == 'Z' || (!value && (returnType == 'L' || returnType == '['))) { - RETURN_TYPE_MISMATCH - } - - overrideReturnValue(value.toHexString(), true) -} - -/** - * Overrides all return statements with a constant `Byte` value. - * All method code is executed the same as unpatched. - * - * @see returnEarly - */ -fun MutableMethod.returnLate(value: Byte) { - check(returnType.first() == 'B') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), true) -} - -/** - * Overrides all return statements with a constant `Short` value. - * All method code is executed the same as unpatched. - * - * @see returnEarly - */ -fun MutableMethod.returnLate(value: Short) { - check(returnType.first() == 'S') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), true) -} - -/** - * Overrides all return statements with a constant `Char` value. - * All method code is executed the same as unpatched. - * - * @see returnEarly - */ -fun MutableMethod.returnLate(value: Char) { - check(returnType.first() == 'C') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.code.toString(), true) -} - -/** - * Overrides all return statements with a constant `Int` value. - * All method code is executed the same as unpatched. - * - * @see returnEarly - */ -fun MutableMethod.returnLate(value: Int) { - check(returnType.first() == 'I') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), true) -} - -/** - * Overrides all return statements with a constant `Long` value. - * All method code is executed the same as unpatched. - * - * @see returnEarly - */ -fun MutableMethod.returnLate(value: Long) { - check(returnType.first() == 'J') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), true) -} - -/** - * Overrides all return statements with a constant `Float` value. - * All method code is executed the same as unpatched. - * - * @see returnEarly - */ -fun MutableMethod.returnLate(value: Float) { - check(returnType.first() == 'F') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), true) -} +fun MutableMethod.returnEarly(value: Double) = returnDouble(value, false) /** * Overrides all return statements with a constant `Double` value. @@ -1043,75 +1051,164 @@ fun MutableMethod.returnLate(value: Float) { * * @see returnEarly */ -fun MutableMethod.returnLate(value: Double) { - check(returnType.first() == 'D') { RETURN_TYPE_MISMATCH } - overrideReturnValue(value.toString(), true) -} +fun MutableMethod.returnLate(value: Double) = returnDouble(value, true) -/** - * Overrides all return statements with a constant String value. - * All method code is executed the same as unpatched. - * - * Target method must have return type - * Ljava/lang/String; or Ljava/lang/CharSequence; - * - * @see returnEarly - */ -fun MutableMethod.returnLate(value: String) { - check(returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;") { - RETURN_TYPE_MISMATCH - } - overrideReturnValue(value, true) -} - -private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean) { - val instructions = if (returnType == "Ljava/lang/String;" || returnType == "Ljava/lang/CharSequence;" ) { - """ - const-string v0, "$value" - return-object v0 - """ - } else when (returnType.first()) { - // If return type is an object, always return null. - 'L', '[' -> { - """ +private fun MutableMethod.overrideReturnValue(value: EncodedValue?, returnLate: Boolean) { + val instructions = if (value == null) { + require(!returnLate) { + "Cannot return late for method of void type" + } + "return-void" + } else { + val encodedValue = DexFormatter.INSTANCE.getEncodedValue(value) + when (value) { + is NullEncodedValue -> { + """ const/4 v0, 0x0 return-object v0 - """ - } + """ + } - 'V' -> { - "return-void" - } + is StringEncodedValue -> { + """ + const-string v0, $encodedValue + return-object v0 + """ + } - 'B', 'Z' -> { - """ - const/4 v0, $value - return v0 - """ - } + is ByteEncodedValue -> { + if (returnType == "B") { + """ + const/4 v0, $encodedValue + return v0 + """ + } else { + """ + const/4 v0, $encodedValue + invoke-static { v0 }, Ljava/lang/Byte;->valueOf(B)Ljava/lang/Byte; + move-result-object v0 + return-object v0 + """ + } + } - 'S', 'C' -> { - """ - const/16 v0, $value - return v0 - """ - } + is BooleanEncodedValue -> { + val encodedValue = value.value.toHexString() + if (returnType == "Z") { + """ + const/4 v0, $encodedValue + return v0 + """ + } else { + """ + const/4 v0, $encodedValue + invoke-static { v0 }, Ljava/lang/Boolean;->valueOf(Z)Ljava/lang/Boolean; + move-result-object v0 + return-object v0 + """ + } + } - 'I', 'F' -> { - """ - const v0, $value - return v0 - """ - } + is ShortEncodedValue -> { + if (returnType == "S") { + """ + const/16 v0, $encodedValue + return v0 + """ + } else { + """ + const/16 v0, $encodedValue + invoke-static { v0 }, Ljava/lang/Short;->valueOf(S)Ljava/lang/Short; + move-result-object v0 + return-object v0 + """ + } + } - 'J', 'D' -> { - """ - const-wide v0, $value - return-wide v0 - """ - } + is CharEncodedValue -> { + if (returnType == "C") { + """ + const/16 v0, $encodedValue + return v0 + """ + } else { + """ + const/16 v0, $encodedValue + invoke-static { v0 }, Ljava/lang/Character;->valueOf(C)Ljava/lang/Character; + move-result-object v0 + return-object v0 + """ + } + } - else -> throw Exception("Return type is not supported: $this") + is IntEncodedValue -> { + if (returnType == "I") { + """ + const v0, $encodedValue + return v0 + """ + } else { + """ + const v0, $encodedValue + invoke-static { v0 }, Ljava/lang/Integer;->valueOf(I)Ljava/lang/Integer; + move-result-object v0 + return-object v0 + """ + } + } + + is FloatEncodedValue -> { + val encodedValue = "${encodedValue}f" + if (returnType == "F") { + """ + const v0, $encodedValue + return v0 + """ + } else { + """ + const v0, $encodedValue + invoke-static { v0 }, Ljava/lang/Float;->valueOf(F)Ljava/lang/Float; + move-result-object v0 + return-object v0 + """ + } + } + + is LongEncodedValue -> { + val encodedValue = "${encodedValue}L" + if (returnType == "J") { + """ + const-wide v0, $encodedValue + return-wide v0 + """ + } else { + """ + const-wide v0, $encodedValue + invoke-static { v0 }, Ljava/lang/Long;->valueOf(J)Ljava/lang/Long; + move-result-object v0 + return-object v0 + """ + } + } + + is DoubleEncodedValue -> { + if (returnType == "D") { + """ + const-wide v0, $encodedValue + return-wide v0 + """ + } else { + """ + const-wide v0, $encodedValue + invoke-static { v0 }, Ljava/lang/Double;->valueOf(D)Ljava/lang/Double; + move-result-object v0 + return-object v0 + """ + } + } + + else -> throw IllegalArgumentException("Value $value cannot be returned from $this") + } } if (returnLate) { diff --git a/patches/src/main/kotlin/app/revanced/util/Utils.kt b/patches/src/main/kotlin/app/revanced/util/Utils.kt index ef7d0ef1a..6305809ad 100644 --- a/patches/src/main/kotlin/app/revanced/util/Utils.kt +++ b/patches/src/main/kotlin/app/revanced/util/Utils.kt @@ -7,4 +7,21 @@ internal object Utils { .trimIndent() // Remove the leading newline. } -internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0" \ No newline at end of file +internal fun Boolean.toHexString(): String = if (this) "0x1" else "0x0" + +internal fun Class<*>.allAssignableTypes(): Set> { + val result = mutableSetOf>() + + fun visit(child: Class<*>?) { + if (child == null || !result.add(child)) { + return + } + + child.interfaces.forEach(::visit) + visit(child.superclass) + } + + visit(this) + + return result +}