From 44e7dbcf4d7eaf94dd0164baba847d3e19250154 Mon Sep 17 00:00:00 2001 From: ILoveOpenSourceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Wed, 31 Dec 2025 00:02:17 +0530 Subject: [PATCH 01/13] fix(Disney+ - Skip ads): Remove unsupported package names (#6422) --- .../kotlin/app/revanced/patches/disneyplus/SkipAdsPatch.kt | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/disneyplus/SkipAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/disneyplus/SkipAdsPatch.kt index e93cafc0d..5b0f551cd 100644 --- a/patches/src/main/kotlin/app/revanced/patches/disneyplus/SkipAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/disneyplus/SkipAdsPatch.kt @@ -8,11 +8,7 @@ val skipAdsPatch = bytecodePatch( name = "Skip ads", description = "Automatically skips ads.", ) { - compatibleWith( - "com.disney.disneyplus", - "in.startv.hotstar", - "in.startv.hotstaronly", - ) + compatibleWith("com.disney.disneyplus") execute { arrayOf(insertionGetPointsFingerprint, insertionGetRangesFingerprint).forEach { From da836b667c455f6e77a73b41460bd281dac0943c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 30 Dec 2025 18:37:28 +0000 Subject: [PATCH 02/13] chore: Release v5.48.0-dev.5 [skip ci] # [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30) ### Bug Fixes * **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4a594855..608009cb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30) + + +### Bug Fixes + +* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154)) + # [5.48.0-dev.4](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.3...v5.48.0-dev.4) (2025-12-29) diff --git a/gradle.properties b/gradle.properties index 00a1ebc1d..97544a540 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M org.gradle.parallel = true android.useAndroidX = true kotlin.code.style = official -version = 5.48.0-dev.4 +version = 5.48.0-dev.5 From 789f0a562861825065633d172445ebf35a1ba8d8 Mon Sep 17 00:00:00 2001 From: xehpuk Date: Sun, 4 Jan 2026 03:03:44 +0100 Subject: [PATCH 03/13] fix: Fix build error introduced in `4046bee` (#6417) --- patches/api/patches.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/api/patches.api b/patches/api/patches.api index 933e267e5..b2d0eaa09 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1192,7 +1192,7 @@ public final class app/revanced/patches/strava/password/EnablePasswordLoginPatch public static final fun getEnablePasswordLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/strava/snowplow/BlockSnowplowTrackingPatchKt { +public final class app/revanced/patches/strava/privacy/BlockSnowplowTrackingPatchKt { public static final fun getBlockSnowplowTrackingPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } From 43ab29d03d82b55f06e93290b4f4e59bef58f599 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 4 Jan 2026 02:07:07 +0000 Subject: [PATCH 04/13] chore: Release v5.48.0-dev.6 [skip ci] # [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04) ### Bug Fixes * Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 608009cb8..cd5f243ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04) + + +### Bug Fixes + +* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8)) + # [5.48.0-dev.5](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.4...v5.48.0-dev.5) (2025-12-30) diff --git a/gradle.properties b/gradle.properties index 97544a540..38f8c15ff 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M org.gradle.parallel = true android.useAndroidX = true kotlin.code.style = official -version = 5.48.0-dev.5 +version = 5.48.0-dev.6 From b42ae27ce66ebad9e9cfc5b70fc121df5bad7567 Mon Sep 17 00:00:00 2001 From: xehpuk Date: Sun, 4 Jan 2026 03:36:08 +0100 Subject: [PATCH 05/13] 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 +} From f5cbb31724d15f7e939b96ee0186fd0a108f9fdc Mon Sep 17 00:00:00 2001 From: xehpuk Date: Sun, 4 Jan 2026 03:38:04 +0100 Subject: [PATCH 06/13] feat(Strava): Add `Disable Quick Edit` patch (#6452) Co-authored-by: oSumAtrIX --- patches/api/patches.api | 4 ++++ .../strava/quickedit/DisableQuickEditPatch.kt | 16 ++++++++++++++++ .../patches/strava/quickedit/Fingerprints.kt | 10 ++++++++++ 3 files changed, 30 insertions(+) create mode 100644 patches/src/main/kotlin/app/revanced/patches/strava/quickedit/DisableQuickEditPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/strava/quickedit/Fingerprints.kt diff --git a/patches/api/patches.api b/patches/api/patches.api index 63a56070a..439040929 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -1200,6 +1200,10 @@ public final class app/revanced/patches/strava/privacy/BlockSnowplowTrackingPatc public static final fun getBlockSnowplowTrackingPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/strava/quickedit/DisableQuickEditPatchKt { + public static final fun getDisableQuickEditPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/strava/subscription/UnlockSubscriptionPatchKt { public static final fun getUnlockSubscriptionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/DisableQuickEditPatch.kt b/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/DisableQuickEditPatch.kt new file mode 100644 index 000000000..128f86870 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/DisableQuickEditPatch.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.strava.quickedit + +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.returnEarly + +@Suppress("unused") +val disableQuickEditPatch = bytecodePatch( + name = "Disable Quick Edit", + description = "Prevents the Quick Edit prompt from popping up.", +) { + compatibleWith("com.strava") + + execute { + getHasAccessToQuickEditFingerprint.method.returnEarly() + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/Fingerprints.kt new file mode 100644 index 000000000..acd48542b --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/strava/quickedit/Fingerprints.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.strava.quickedit + +import app.revanced.patcher.fingerprint + +internal val getHasAccessToQuickEditFingerprint = fingerprint { + returns("Z") + custom { method, _ -> + method.name == "getHasAccessToQuickEdit" + } +} From 1cc2cb9cb28dd1b79529ee1d917221381b55d916 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 4 Jan 2026 02:43:29 +0000 Subject: [PATCH 07/13] chore: Release v5.48.0-dev.7 [skip ci] # [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04) ### Features * **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc)) * **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567)) --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd5f243ea..5d37e7769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04) + + +### Features + +* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc)) +* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567)) + # [5.48.0-dev.6](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.5...v5.48.0-dev.6) (2026-01-04) diff --git a/gradle.properties b/gradle.properties index 38f8c15ff..aa4a96d3a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M org.gradle.parallel = true android.useAndroidX = true kotlin.code.style = official -version = 5.48.0-dev.6 +version = 5.48.0-dev.7 From d25dcfe49ac331c9b3dca739ba0be95dbab669cc Mon Sep 17 00:00:00 2001 From: Swakshan <56347042+Swakshan@users.noreply.github.com> Date: Sun, 4 Jan 2026 18:44:29 +0530 Subject: [PATCH 08/13] feat(Letterboxd): Add `Unlock app icons` patch (#6415) --- patches/api/patches.api | 4 ++++ .../unlock/unlockAppIcons/Fingerprints.kt | 9 +++++++++ .../unlock/unlockAppIcons/UnlockAppIconsPatch.kt | 16 ++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatch.kt diff --git a/patches/api/patches.api b/patches/api/patches.api index 439040929..9b5ac4d0c 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -352,6 +352,10 @@ public final class app/revanced/patches/letterboxd/ads/HideAdsPatchKt { public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatchKt { + public static final fun getUnlockAppIconsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/lightroom/misc/login/DisableMandatoryLoginPatchKt { public static final fun getDisableMandatoryLoginPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/Fingerprints.kt new file mode 100644 index 000000000..1b549cd57 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/Fingerprints.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.letterboxd.unlock.unlockAppIcons + +import app.revanced.patcher.fingerprint + +internal val getCanChangeAppIconFingerprint = fingerprint { + custom { method, classDef -> + method.name == "getCanChangeAppIcon" && classDef.type.endsWith("SettingsAppIconFragment;") + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatch.kt new file mode 100644 index 000000000..54d6f3df9 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/letterboxd/unlock/unlockAppIcons/UnlockAppIconsPatch.kt @@ -0,0 +1,16 @@ + +package app.revanced.patches.letterboxd.unlock.unlockAppIcons + +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.returnEarly + +@Suppress("unused") +val unlockAppIconsPatch = bytecodePatch( + name = "Unlock app icons", +) { + compatibleWith("com.letterboxd.letterboxd") + + execute { + getCanChangeAppIconFingerprint.method.returnEarly(true) + } +} From a3f7609fe38033975d7c52e63e14b8b09902529f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 4 Jan 2026 13:17:54 +0000 Subject: [PATCH 09/13] chore: Release v5.48.0-dev.8 [skip ci] # [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04) ### Features * **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc)) --- CHANGELOG.md | 7 +++++++ gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d37e7769..d9f397d0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04) + + +### Features + +* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc)) + # [5.48.0-dev.7](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.6...v5.48.0-dev.7) (2026-01-04) diff --git a/gradle.properties b/gradle.properties index aa4a96d3a..5487ca1f0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M org.gradle.parallel = true android.useAndroidX = true kotlin.code.style = official -version = 5.48.0-dev.7 +version = 5.48.0-dev.8 From 3d754575a4db90a2138b5deedf24f70bc72ddaf7 Mon Sep 17 00:00:00 2001 From: Ushie Date: Thu, 8 Jan 2026 00:31:15 +0300 Subject: [PATCH 10/13] ci: Simplify Crowdin translation file destination path (#6463) --- crowdin.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crowdin.yml b/crowdin.yml index 148f321cd..81022c88c 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,8 +1,9 @@ project_id_env: "CROWDIN_PROJECT_ID" api_token_env: "CROWDIN_PERSONAL_TOKEN" -preserve_hierarchy: false +preserve_hierarchy: true files: - source: patches/src/main/resources/addresources/values/strings.xml + dest: patches.xml translation: patches/src/main/resources/addresources/values-%android_code%/strings.xml skip_untranslated_strings: true From 6312fe8d60da24465c0c1b0fa4e94ceb79873d9c Mon Sep 17 00:00:00 2001 From: 1fexd <58902674+1fexd@users.noreply.github.com> Date: Thu, 8 Jan 2026 00:06:05 +0000 Subject: [PATCH 11/13] feat: Disable Play Integrity patch (#6412) Co-authored-by: oSumAtrIX --- .../disable-play-integrity/build.gradle.kts | 20 ++++++ .../src/main/AndroidManifest.xml | 1 + .../protocol/IExpressIntegrityService.aidl | 8 +++ .../IExpressIntegrityServiceCallback.aidl | 5 ++ .../integrity/protocol/IIntegrityService.aidl | 8 +++ .../protocol/IIntegrityServiceCallback.aidl | 7 +++ .../src/main/java/android/ext/PackageId.java | 10 +++ .../main/java/android/os/BinderWrapper.java | 62 +++++++++++++++++++ .../ClassicPlayIntegrityServiceWrapper.java | 41 ++++++++++++ .../PlayIntegrityServiceWrapper.java | 48 ++++++++++++++ .../lib/playintegrity/PlayIntegrityUtils.java | 35 +++++++++++ .../StandardPlayIntegrityServiceWrapper.java | 42 +++++++++++++ .../lib/util/ServiceConnectionWrapper.java | 49 +++++++++++++++ .../DisablePlayIntegrityPatch.java | 17 +++++ .../internal/os/FakeBackgroundHandler.java | 11 ++++ patches/api/patches.api | 4 ++ .../playintegrity/DisablePlayIntegrity.kt | 55 ++++++++++++++++ 17 files changed, 423 insertions(+) create mode 100644 extensions/all/misc/disable-play-integrity/build.gradle.kts create mode 100644 extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml create mode 100644 extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl create mode 100644 extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl create mode 100644 extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl create mode 100644 extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl create mode 100644 extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java create mode 100644 extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java create mode 100644 extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java create mode 100644 extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java create mode 100644 extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java create mode 100644 extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java create mode 100644 extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java create mode 100644 extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java create mode 100644 extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt diff --git a/extensions/all/misc/disable-play-integrity/build.gradle.kts b/extensions/all/misc/disable-play-integrity/build.gradle.kts new file mode 100644 index 000000000..549297227 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/build.gradle.kts @@ -0,0 +1,20 @@ +android { + namespace = "app.revanced.extension" + + defaultConfig { + minSdk = 21 + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + buildFeatures { + aidl = true + } +} + +dependencies { + compileOnly(libs.annotation) +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml b/extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9b65eb06c --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl new file mode 100644 index 000000000..7b8f59f1d --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityService.aidl @@ -0,0 +1,8 @@ +package com.google.android.play.core.integrity.protocol; + +import android.os.Bundle; +import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback; + +interface IExpressIntegrityService { + oneway void requestIntegrityToken(in Bundle request, IExpressIntegrityServiceCallback callback) = 2; +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl new file mode 100644 index 000000000..624167afb --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IExpressIntegrityServiceCallback.aidl @@ -0,0 +1,5 @@ +package com.google.android.play.core.integrity.protocol; + +interface IExpressIntegrityServiceCallback { + oneway void onRequestExpressIntegrityTokenResult(in Bundle result) = 2; +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl new file mode 100644 index 000000000..bb1bcd551 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityService.aidl @@ -0,0 +1,8 @@ +package com.google.android.play.core.integrity.protocol; + +import android.os.Bundle; +import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback; + +interface IIntegrityService { + oneway void requestIntegrityToken(in Bundle request, IIntegrityServiceCallback callback) = 1; +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl new file mode 100644 index 000000000..9485ec169 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/aidl/com/google/android/play/core/integrity/protocol/IIntegrityServiceCallback.aidl @@ -0,0 +1,7 @@ +package com.google.android.play.core.integrity.protocol; + +import android.os.Bundle; + +interface IIntegrityServiceCallback { + oneway void onResult(in Bundle result) = 1; +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java b/extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java new file mode 100644 index 000000000..31c2ca6db --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/java/android/ext/PackageId.java @@ -0,0 +1,10 @@ +package android.ext; +/** @hide */ +// Int values that are assigned to packages in this interface can be retrieved at runtime from +// ApplicationInfo.ext().getPackageId() or from AndroidPackage.ext().getPackageId() (in system_server). +// +// PackageIds are assigned to parsed APKs only after they are verified, either by a certificate check +// or by a check that the APK is stored on an immutable OS partition. +public interface PackageId { + String PLAY_STORE_NAME = "com.android.vending"; +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java new file mode 100644 index 000000000..a01806441 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/java/android/os/BinderWrapper.java @@ -0,0 +1,62 @@ +package android.os; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.FileDescriptor; + +/** @hide */ +public class BinderWrapper implements IBinder { + protected final IBinder base; + + public BinderWrapper(IBinder base) { + this.base = base; + } + + @Override + public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { + return base.transact(code, data, reply, flags); + } + + @Nullable + @Override + public IInterface queryLocalInterface(@NonNull String descriptor) { + return base.queryLocalInterface(descriptor); + } + + @Nullable + @Override + public String getInterfaceDescriptor() throws RemoteException { + return base.getInterfaceDescriptor(); + } + + @Override + public boolean pingBinder() { + return base.pingBinder(); + } + + @Override + public boolean isBinderAlive() { + return base.isBinderAlive(); + } + + @Override + public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException { + base.dump(fd, args); + } + + @Override + public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException { + base.dumpAsync(fd, args); + } + + @Override + public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException { + base.linkToDeath(recipient, flags); + } + + @Override + public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { + return base.unlinkToDeath(recipient, flags); + } +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java new file mode 100644 index 000000000..3bd88d2a6 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/ClassicPlayIntegrityServiceWrapper.java @@ -0,0 +1,41 @@ +package app.grapheneos.gmscompat.lib.playintegrity; + +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.os.FakeBackgroundHandler; +import com.google.android.play.core.integrity.protocol.IIntegrityService; +import com.google.android.play.core.integrity.protocol.IIntegrityServiceCallback; + +class ClassicPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper { + + ClassicPlayIntegrityServiceWrapper(IBinder base) { + super(base); + requestIntegrityTokenTxnCode = 2; // IIntegrityService.Stub.TRANSACTION_requestIntegrityToken + } + + static class TokenRequestStub extends IIntegrityService.Stub { + public void requestIntegrityToken(Bundle request, IIntegrityServiceCallback callback) { + Runnable r = () -> { + var result = new Bundle(); + // https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/IntegrityErrorCode.html#API_NOT_AVAILABLE + final int API_NOT_AVAILABLE = -1; + result.putInt("error", API_NOT_AVAILABLE); + try { + callback.onResult(result); + } catch (RemoteException e) { + Log.e("IIntegrityService.Stub", "", e); + } + }; + FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay()); + } + }; + + @Override + protected Binder createTokenRequestStub() { + return new TokenRequestStub(); + } +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java new file mode 100644 index 000000000..0418b4fe7 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityServiceWrapper.java @@ -0,0 +1,48 @@ +package app.grapheneos.gmscompat.lib.playintegrity; + +import android.os.Binder; +import android.os.BinderWrapper; +import android.os.IBinder; +import android.os.Parcel; +import android.os.RemoteException; +import android.util.Log; + +import androidx.annotation.Nullable; + +abstract class PlayIntegrityServiceWrapper extends BinderWrapper { + final String TAG; + protected int requestIntegrityTokenTxnCode; + + public PlayIntegrityServiceWrapper(IBinder base) { + super(base); + TAG = getClass().getSimpleName(); + } + + protected abstract Binder createTokenRequestStub(); + + @Override + public boolean transact(int code, Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { + if (code == requestIntegrityTokenTxnCode) { + if (maybeStubOutIntegrityTokenRequest(code, data, reply, flags)) { + return true; + } + } + return super.transact(code, data, reply, flags); + } + + private boolean maybeStubOutIntegrityTokenRequest(int code, Parcel data, @Nullable Parcel reply, int flags) { + Log.d(TAG, "integrity token request detected"); + + try { + createTokenRequestStub().transact(code, data, reply, flags); + } catch (RemoteException e) { + // this is a local call + throw new IllegalStateException(e); + } + return true; + } + + protected static long getTokenRequestResultDelay() { + return 500L; + } +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java new file mode 100644 index 000000000..6ff4720cc --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/PlayIntegrityUtils.java @@ -0,0 +1,35 @@ +package app.grapheneos.gmscompat.lib.playintegrity; + +import android.content.Intent; +import android.content.ServiceConnection; +import android.ext.PackageId; +import android.os.IBinder; +import androidx.annotation.Nullable; +import app.grapheneos.gmscompat.lib.util.ServiceConnectionWrapper; +import java.util.function.UnaryOperator; + +public class PlayIntegrityUtils { + + public static @Nullable ServiceConnection maybeReplaceServiceConnection(Intent service, ServiceConnection orig) { + if (PackageId.PLAY_STORE_NAME.equals(service.getPackage())) { + UnaryOperator binderOverride = null; + + final String CLASSIC_SERVICE = + "com.google.android.play.core.integrityservice.BIND_INTEGRITY_SERVICE"; + final String STANDARD_SERVICE = + "com.google.android.play.core.expressintegrityservice.BIND_EXPRESS_INTEGRITY_SERVICE"; + + String action = service.getAction(); + if (STANDARD_SERVICE.equals(action)) { + binderOverride = StandardPlayIntegrityServiceWrapper::new; + } else if (CLASSIC_SERVICE.equals(action)) { + binderOverride = ClassicPlayIntegrityServiceWrapper::new; + } + + if (binderOverride != null) { + return new ServiceConnectionWrapper(orig, binderOverride); + } + } + return null; + } +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java new file mode 100644 index 000000000..c1c4937f0 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/playintegrity/StandardPlayIntegrityServiceWrapper.java @@ -0,0 +1,42 @@ +package app.grapheneos.gmscompat.lib.playintegrity; + +import android.annotation.SuppressLint; +import android.os.Binder; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import com.android.internal.os.FakeBackgroundHandler; +import com.google.android.play.core.integrity.protocol.IExpressIntegrityService; +import com.google.android.play.core.integrity.protocol.IExpressIntegrityServiceCallback; + +@SuppressLint("LongLogTag") +class StandardPlayIntegrityServiceWrapper extends PlayIntegrityServiceWrapper { + + StandardPlayIntegrityServiceWrapper(IBinder base) { + super(base); + requestIntegrityTokenTxnCode = 3; // IExpressIntegrityService.Stub.TRANSACTION_requestIntegrityToken + } + + static class TokenRequestStub extends IExpressIntegrityService.Stub { + public void requestIntegrityToken(Bundle request, IExpressIntegrityServiceCallback callback) { + Runnable r = () -> { + var result = new Bundle(); + // https://developer.android.com/google/play/integrity/reference/com/google/android/play/core/integrity/model/StandardIntegrityErrorCode.html#API_NOT_AVAILABLE + final int API_NOT_AVAILABLE = -1; + result.putInt("error", API_NOT_AVAILABLE); + try { + callback.onRequestExpressIntegrityTokenResult(result); + } catch (RemoteException e) { + Log.e("IExpressIntegrityService.Stub", "", e); + } + }; + FakeBackgroundHandler.getHandler().postDelayed(r, getTokenRequestResultDelay()); + } + }; + + @Override + protected Binder createTokenRequestStub() { + return new TokenRequestStub(); + } +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java new file mode 100644 index 000000000..9edfc39f8 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/grapheneos/gmscompat/lib/util/ServiceConnectionWrapper.java @@ -0,0 +1,49 @@ +package app.grapheneos.gmscompat.lib.util; + +import android.content.ComponentName; +import android.content.ServiceConnection; +import android.os.Build; +import android.os.IBinder; + +import java.util.function.UnaryOperator; + +public class ServiceConnectionWrapper implements ServiceConnection { + private final ServiceConnection base; + private final UnaryOperator binderOverride; + + public ServiceConnectionWrapper(ServiceConnection base, UnaryOperator binderOverride) { + this.base = base; + this.binderOverride = binderOverride; + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + IBinder override = binderOverride.apply(service); + if (override != null) { + service = override; + } + } + + base.onServiceConnected(name, service); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + base.onServiceDisconnected(name); + } + + @Override + public void onBindingDied(ComponentName name) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + base.onBindingDied(name); + } + } + + @Override + public void onNullBinding(ComponentName name) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + base.onNullBinding(name); + } + } +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java b/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java new file mode 100644 index 000000000..a27e56be9 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/java/app/revanced/extension/playintegrity/DisablePlayIntegrityPatch.java @@ -0,0 +1,17 @@ +package app.revanced.extension.playintegrity; + +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import app.grapheneos.gmscompat.lib.playintegrity.PlayIntegrityUtils; + +public class DisablePlayIntegrityPatch { + public static boolean bindService(Context context, Intent service, ServiceConnection conn, int flags) { + ServiceConnection override = PlayIntegrityUtils.maybeReplaceServiceConnection(service, conn); + if (override != null) { + conn = override; + } + + return context.bindService(service, conn, flags); + } +} diff --git a/extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java b/extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java new file mode 100644 index 000000000..6b4cb92b4 --- /dev/null +++ b/extensions/all/misc/disable-play-integrity/src/main/java/com/android/internal/os/FakeBackgroundHandler.java @@ -0,0 +1,11 @@ +package com.android.internal.os; + +import android.os.Handler; +import android.os.Looper; + +public class FakeBackgroundHandler { + + public static Handler getHandler() { + return new Handler(Looper.getMainLooper()); + } +} diff --git a/patches/api/patches.api b/patches/api/patches.api index 9b5ac4d0c..ec75df2b3 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -104,6 +104,10 @@ public final class app/revanced/patches/all/misc/packagename/ChangePackageNamePa public static final fun setPackageNameOption (Lapp/revanced/patcher/patch/Option;)V } +public final class app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrityKt { + public static final fun getDisablePlayIntegrityPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/all/misc/resources/AddResourcesPatchKt { public static final fun addResource (Ljava/lang/String;Lapp/revanced/util/resource/BaseResource;)Z public static final fun addResources (Lapp/revanced/patcher/patch/Patch;Lkotlin/jvm/functions/Function1;)Z diff --git a/patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt b/patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt new file mode 100644 index 000000000..25a948e34 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/all/misc/playintegrity/DisablePlayIntegrity.kt @@ -0,0 +1,55 @@ +package app.revanced.patches.all.misc.playintegrity + +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.transformation.transformInstructionsPatch +import app.revanced.util.getReference +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference +import com.android.tools.smali.dexlib2.util.MethodUtil + +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/playintegrity/DisablePlayIntegrityPatch;" + +private val CONTEXT_BIND_SERVICE_METHOD_REFERENCE = ImmutableMethodReference( + "Landroid/content/Context;", + "bindService", + listOf("Landroid/content/Intent;", "Landroid/content/ServiceConnection;", "I"), + "Z" +) + + +@Suppress("unused") +val disablePlayIntegrityPatch = bytecodePatch( + name = "Disable Play Integrity", + description = "Prevents apps from using Play Integrity by pretending it is not available.", + use = false, +) { + extendWith("extensions/all/misc/disable-play-integrity.rve") + + dependsOn( + transformInstructionsPatch( + filterMap = filterMap@{ classDef, method, instruction, instructionIndex -> + val reference = instruction + .getReference() + ?.takeIf { + MethodUtil.methodSignaturesMatch(CONTEXT_BIND_SERVICE_METHOD_REFERENCE, it) + } + ?: return@filterMap null + + Triple(instruction as Instruction35c, instructionIndex, reference.parameterTypes) + }, + transform = { method, entry -> + val (instruction, index, parameterTypes) = entry + val parameterString = parameterTypes.joinToString(separator = "") + val registerString = "v${instruction.registerC}, v${instruction.registerD}, v${instruction.registerE}, v${instruction.registerF}" + + method.replaceInstruction( + index, + "invoke-static { $registerString }, $EXTENSION_CLASS_DESCRIPTOR->bindService(Landroid/content/Context;$parameterString)Z" + ) + } + ) + ) +} From 4cc315952db557c565872de9e8484805f2e42305 Mon Sep 17 00:00:00 2001 From: xehpuk Date: Thu, 8 Jan 2026 01:08:52 +0100 Subject: [PATCH 12/13] feat: Add `Disable Sentry telemetry` patch (#6416) --- patches/api/patches.api | 4 +++ .../misc/privacy/DisableSentryTelemetry.kt | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/privacy/DisableSentryTelemetry.kt diff --git a/patches/api/patches.api b/patches/api/patches.api index ec75df2b3..86d8f980b 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -944,6 +944,10 @@ public final class app/revanced/patches/shared/misc/pairip/license/DisableLicens public static final fun getDisableLicenseCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/shared/misc/privacy/DisableSentryTelemetryKt { + public static final fun getDisableSentryTelemetryPatch ()Lapp/revanced/patcher/patch/ResourcePatch; +} + public final class app/revanced/patches/shared/misc/settings/SettingsPatchKt { public static final fun overrideThemeColors (Ljava/lang/String;Ljava/lang/String;)V public static final fun settingsPatch (Ljava/util/List;Ljava/util/Set;)Lapp/revanced/patcher/patch/ResourcePatch; diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/privacy/DisableSentryTelemetry.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/privacy/DisableSentryTelemetry.kt new file mode 100644 index 000000000..f361a26bb --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/privacy/DisableSentryTelemetry.kt @@ -0,0 +1,36 @@ +package app.revanced.patches.shared.misc.privacy + +import app.revanced.patcher.patch.resourcePatch +import app.revanced.util.asSequence +import app.revanced.util.getNode +import org.w3c.dom.Element + +@Suppress("unused") +val disableSentryTelemetryPatch = resourcePatch( + name = "Disable Sentry telemetry", + description = "Disables Sentry telemetry. See https://sentry.io/for/android/ for more information.", + use = false, +) { + execute { + fun Element.replaceOrCreate(tagName: String, attributeName: String, attributeValue: String) { + val childElements = getElementsByTagName(tagName).asSequence().filterIsInstance() + val targetChild = childElements.find { childElement -> + childElement.getAttribute("android:name") == attributeName + } + if (targetChild != null) { + targetChild.setAttribute("android:value", attributeValue) + } else { + appendChild(ownerDocument.createElement(tagName).apply { + setAttribute("android:name", attributeName) + setAttribute("android:value", attributeValue) + }) + } + } + + document("AndroidManifest.xml").use { document -> + val application = document.getNode("application") as Element + application.replaceOrCreate("meta-data", "io.sentry.enabled", "false") + application.replaceOrCreate("meta-data", "io.sentry.dsn", "") + } + } +} From f4af27dfecf24af25d1c1650bfd573485040a79c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 8 Jan 2026 00:11:57 +0000 Subject: [PATCH 13/13] chore: Release v5.48.0-dev.9 [skip ci] # [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08) ### Features * Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305)) * Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c)) --- CHANGELOG.md | 8 ++++++++ gradle.properties | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f397d0c..b70fb7efc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# [5.48.0-dev.9](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.8...v5.48.0-dev.9) (2026-01-08) + + +### Features + +* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305)) +* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c)) + # [5.48.0-dev.8](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.7...v5.48.0-dev.8) (2026-01-04) diff --git a/gradle.properties b/gradle.properties index 5487ca1f0..816fb3e47 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M org.gradle.parallel = true android.useAndroidX = true kotlin.code.style = official -version = 5.48.0-dev.8 +version = 5.48.0-dev.9