From fa552297c55dcf894e900c4069f3e8672bf3a1dd Mon Sep 17 00:00:00 2001 From: hoodles <207470673+hoo-dles@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:20:16 -0700 Subject: [PATCH 1/5] feat(Crunchyroll): Add `Disable ads` patch --- patches/api/patches.api | 4 ++ .../crunchyroll/ads/DisableAdsPatch.kt | 43 +++++++++++++++++++ .../patches/crunchyroll/ads/Fingerprints.kt | 7 +++ .../kotlin/app/revanced/util/BytecodeUtils.kt | 9 ++++ 4 files changed, 63 insertions(+) create mode 100644 patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/Fingerprints.kt diff --git a/patches/api/patches.api b/patches/api/patches.api index be23caa1c9..fc9ad0b241 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -160,6 +160,10 @@ public final class app/revanced/patches/cieid/restrictions/root/BypassRootChecks public static final fun getBypassRootChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/crunchyroll/ads/DisableAdsPatchKt { + public static final fun getDisableAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/duolingo/ad/DisableAdsPatchKt { public static final fun getDisableAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt new file mode 100644 index 0000000000..2b8b57484d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt @@ -0,0 +1,43 @@ +package app.revanced.patches.crunchyroll.ads + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.extensions.InstructionExtensions.instructions +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.util.getReference +import app.revanced.util.removeFlag +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference + +@Suppress("unused") +val disableAdsPatch = bytecodePatch( + name = "Disable Ads" +) { + compatibleWith("com.crunchyroll.crunchyroid") + + execute { + val enableAdsField = videoUrlReadyToStringFingerprint.let { + val strIndex = videoUrlReadyToStringFingerprint.stringMatches!!.last().index + // The iget-xxx should always be after const-string and invoke-virtual (StringBuilder.append()). + it.method.getInstruction(strIndex + 2).getReference()!! + } + + // Remove final access flag on field. + videoUrlReadyToStringFingerprint.classDef.fields + .first { it.name == enableAdsField.name } + .removeFlag(AccessFlags.FINAL) + + // Override field non-default constructor (has parameters). + val constructor = videoUrlReadyToStringFingerprint.classDef.methods.first { + AccessFlags.CONSTRUCTOR.isSet(it.accessFlags) && it.parameters.isNotEmpty() + } + // "this" reference is previously moved from p0 to v0. + constructor.addInstructions( + constructor.instructions.count() - 1, + """ + const/4 v1, 0x0 + iput-boolean v1, v0, ${enableAdsField.definingClass}->${enableAdsField.name}:Z + """.trimIndent()) + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/Fingerprints.kt new file mode 100644 index 0000000000..0266e0344d --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/Fingerprints.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.crunchyroll.ads + +import app.revanced.patcher.fingerprint + +internal val videoUrlReadyToStringFingerprint = fingerprint { + strings("VideoUrlReady(url=", ", enableAds=") +} diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 047f21cc43..a8bab80445 100644 --- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -10,6 +10,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.patch.BytecodePatchContext import app.revanced.patcher.patch.PatchException import app.revanced.patcher.util.proxy.mutableTypes.MutableClass +import app.revanced.patcher.util.proxy.mutableTypes.MutableField import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.smali.ExternalLabel @@ -1021,6 +1022,14 @@ private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean } } +/** + * Remove the given AccessFlags from the field. + */ +internal fun MutableField.removeFlag(vararg flags: AccessFlags) { + val bitField = flags.map { it.value }.reduce { acc, flag -> acc and flag } + this.accessFlags = this.accessFlags and bitField.inv() +} + internal fun BytecodePatchContext.addStaticFieldToExtension( className: String, methodName: String, From b8e2ee24c8750bb27798bf5eb811e75349b6b33d Mon Sep 17 00:00:00 2001 From: hoodles <207470673+hoo-dles@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:30:39 -0700 Subject: [PATCH 2/5] Updating comments --- .../app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt index 2b8b57484d..6c41244ea4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt @@ -28,11 +28,11 @@ val disableAdsPatch = bytecodePatch( .first { it.name == enableAdsField.name } .removeFlag(AccessFlags.FINAL) - // Override field non-default constructor (has parameters). + // Override enableAds field in non-default constructor. val constructor = videoUrlReadyToStringFingerprint.classDef.methods.first { AccessFlags.CONSTRUCTOR.isSet(it.accessFlags) && it.parameters.isNotEmpty() } - // "this" reference is previously moved from p0 to v0. + // The "this" reference was previously moved from p0 to v0 in the constructor. constructor.addInstructions( constructor.instructions.count() - 1, """ From 5e2f3240ef31a137af434e6432bcd654a3d46405 Mon Sep 17 00:00:00 2001 From: hoodles <207470673+hoo-dles@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:48:52 -0700 Subject: [PATCH 3/5] Updates from PR review --- patches/api/patches.api | 4 ++-- .../ads/{DisableAdsPatch.kt => HideAdsPatch.kt} | 8 ++++---- .../src/main/kotlin/app/revanced/util/BytecodeUtils.kt | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/{DisableAdsPatch.kt => HideAdsPatch.kt} (92%) diff --git a/patches/api/patches.api b/patches/api/patches.api index fc9ad0b241..0c4935b4a6 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -160,8 +160,8 @@ public final class app/revanced/patches/cieid/restrictions/root/BypassRootChecks public static final fun getBypassRootChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } -public final class app/revanced/patches/crunchyroll/ads/DisableAdsPatchKt { - public static final fun getDisableAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +public final class app/revanced/patches/crunchyroll/ads/HideAdsPatchKt { + public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } public final class app/revanced/patches/duolingo/ad/DisableAdsPatchKt { diff --git a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt similarity index 92% rename from patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt rename to patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt index 6c41244ea4..6d7a5bf12e 100644 --- a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/DisableAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt @@ -5,14 +5,14 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.patch.bytecodePatch import app.revanced.util.getReference -import app.revanced.util.removeFlag +import app.revanced.util.removeFlags import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference @Suppress("unused") -val disableAdsPatch = bytecodePatch( - name = "Disable Ads" +val hideAdsPatch = bytecodePatch( + name = "Hide Ads" ) { compatibleWith("com.crunchyroll.crunchyroid") @@ -26,7 +26,7 @@ val disableAdsPatch = bytecodePatch( // Remove final access flag on field. videoUrlReadyToStringFingerprint.classDef.fields .first { it.name == enableAdsField.name } - .removeFlag(AccessFlags.FINAL) + .removeFlags(AccessFlags.FINAL) // Override enableAds field in non-default constructor. val constructor = videoUrlReadyToStringFingerprint.classDef.methods.first { diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index a8bab80445..017a998626 100644 --- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -1025,7 +1025,7 @@ private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean /** * Remove the given AccessFlags from the field. */ -internal fun MutableField.removeFlag(vararg flags: AccessFlags) { +internal fun MutableField.removeFlags(vararg flags: AccessFlags) { val bitField = flags.map { it.value }.reduce { acc, flag -> acc and flag } this.accessFlags = this.accessFlags and bitField.inv() } From bf44558b01e57b0f5ab76fa8ecf28aae66c4543a Mon Sep 17 00:00:00 2001 From: hoodles <207470673+hoo-dles@users.noreply.github.com> Date: Wed, 18 Jun 2025 22:56:25 -0700 Subject: [PATCH 4/5] Addressing additional PR comments --- .../patches/crunchyroll/ads/HideAdsPatch.kt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt index 6d7a5bf12e..46ba842941 100644 --- a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt @@ -5,8 +5,10 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.patch.bytecodePatch import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction import app.revanced.util.removeFlags import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction import com.android.tools.smali.dexlib2.iface.reference.FieldReference @@ -17,10 +19,11 @@ val hideAdsPatch = bytecodePatch( compatibleWith("com.crunchyroll.crunchyroid") execute { + // Get obfuscated "enableAds" field from toString method. val enableAdsField = videoUrlReadyToStringFingerprint.let { val strIndex = videoUrlReadyToStringFingerprint.stringMatches!!.last().index - // The iget-xxx should always be after const-string and invoke-virtual (StringBuilder.append()). - it.method.getInstruction(strIndex + 2).getReference()!! + val fieldIndex = it.method.indexOfFirstInstruction(strIndex, Opcode.IGET_BOOLEAN) + it.method.getInstruction(fieldIndex).getReference()!! } // Remove final access flag on field. @@ -32,12 +35,12 @@ val hideAdsPatch = bytecodePatch( val constructor = videoUrlReadyToStringFingerprint.classDef.methods.first { AccessFlags.CONSTRUCTOR.isSet(it.accessFlags) && it.parameters.isNotEmpty() } - // The "this" reference was previously moved from p0 to v0 in the constructor. constructor.addInstructions( constructor.instructions.count() - 1, """ - const/4 v1, 0x0 - iput-boolean v1, v0, ${enableAdsField.definingClass}->${enableAdsField.name}:Z + move-object/from16 v0, p0 + const/4 v1, 0x0 + iput-boolean v1, v0, ${enableAdsField.definingClass}->${enableAdsField.name}:Z """.trimIndent()) } } \ No newline at end of file From c4f2de807823f08e64ebdab983a748b88db673dd Mon Sep 17 00:00:00 2001 From: hoodles <207470673+hoo-dles@users.noreply.github.com> Date: Wed, 18 Jun 2025 23:08:40 -0700 Subject: [PATCH 5/5] Couple more cleanup changes --- .../app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt index 46ba842941..5344175bcb 100644 --- a/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/crunchyroll/ads/HideAdsPatch.kt @@ -40,7 +40,7 @@ val hideAdsPatch = bytecodePatch( """ move-object/from16 v0, p0 const/4 v1, 0x0 - iput-boolean v1, v0, ${enableAdsField.definingClass}->${enableAdsField.name}:Z - """.trimIndent()) + iput-boolean v1, v0, $enableAdsField + """) } } \ No newline at end of file