From 3c79f3d34d978aead60de19e64d05fbebc1bc73a Mon Sep 17 00:00:00 2001 From: patchink0 Date: Thu, 11 Jul 2024 00:13:44 +0200 Subject: [PATCH] feat(Soundcloud): Add `Hide ads` and `Disable telemetry` patch (#3386) Co-authored-by: bewzusore Co-authored-by: oSumAtrIX --- api/revanced-patches.api | 12 ++++ .../patches/soundcloud/ad/HideAdsPatch.kt | 72 +++++++++++++++++++ .../FeatureConstructorFingerprint.kt | 14 ++++ .../ad/fingerprints/InterceptFingerprint.kt | 22 ++++++ .../UserConsumerPlanConstructorFingerprint.kt | 14 ++++ .../analytics/DisableTelemetryPatch.kt | 24 +++++++ .../CreateTrackingApiFingerprint.kt | 12 ++++ 7 files changed, 170 insertions(+) create mode 100644 src/main/kotlin/app/revanced/patches/soundcloud/ad/HideAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/FeatureConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/InterceptFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/UserConsumerPlanConstructorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/soundcloud/analytics/DisableTelemetryPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/soundcloud/analytics/fingerprints/CreateTrackingApiFingerprint.kt diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 805eda8b..f0e32381 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -1032,6 +1032,18 @@ public final class app/revanced/patches/songpal/badge/RemoveNotificationBadgePat public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } +public final class app/revanced/patches/soundcloud/ad/HideAdsPatch : app/revanced/patcher/patch/BytecodePatch { + public static final field INSTANCE Lapp/revanced/patches/soundcloud/ad/HideAdsPatch; + public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V +} + +public final class app/revanced/patches/soundcloud/analytics/DisableTelemetryPatch : app/revanced/patcher/patch/BytecodePatch { + public static final field INSTANCE Lapp/revanced/patches/soundcloud/analytics/DisableTelemetryPatch; + public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V +} + public final class app/revanced/patches/spotify/layout/theme/CustomThemePatch : app/revanced/patcher/patch/ResourcePatch { public static final field INSTANCE Lapp/revanced/patches/spotify/layout/theme/CustomThemePatch; public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V diff --git a/src/main/kotlin/app/revanced/patches/soundcloud/ad/HideAdsPatch.kt b/src/main/kotlin/app/revanced/patches/soundcloud/ad/HideAdsPatch.kt new file mode 100644 index 00000000..402fa464 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/soundcloud/ad/HideAdsPatch.kt @@ -0,0 +1,72 @@ +package app.revanced.patches.soundcloud.ad + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotation.CompatiblePackage +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.soundcloud.ad.fingerprints.InterceptFingerprint +import app.revanced.patches.soundcloud.ad.fingerprints.FeatureConstructorFingerprint +import app.revanced.patches.soundcloud.ad.fingerprints.UserConsumerPlanConstructorFingerprint +import app.revanced.util.resultOrThrow + +@Patch( + name = "Hide ads", + compatiblePackages = [CompatiblePackage("com.soundcloud.android")], +) +@Suppress("unused") +object HideAdsPatch : BytecodePatch( + setOf(FeatureConstructorFingerprint, UserConsumerPlanConstructorFingerprint, InterceptFingerprint), +) { + override fun execute(context: BytecodeContext) { + // Enable a preset feature to disable audio ads by modifying the JSON server response. + // This method is the constructor of a class representing a "Feature" object parsed from JSON data. + // p1 is the name of the feature. + // p2 is true if the feature is enabled, false otherwise. + FeatureConstructorFingerprint.resultOrThrow().mutableMethod.apply { + val afterCheckNotNullIndex = 2 + addInstructionsWithLabels( + afterCheckNotNullIndex, + """ + const-string v0, "no_audio_ads" + invoke-virtual {p1, v0}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z + move-result v0 + if-eqz v0, :skip + const/4 p2, 0x1 + """, + ExternalLabel("skip", getInstruction(afterCheckNotNullIndex)), + ) + } + + // Overwrite the JSON response from the server to a paid plan, which hides all ads in the app. + // This does not enable paid features, as they are all checked for on the backend. + // This method is the constructor of a class representing a "UserConsumerPlan" object parsed from JSON data. + // p1 is the "currentTier" value, dictating which features to enable in the app. + // p4 is the "consumerPlanUpsells" value, a list of plans to try to sell to the user. + // p5 is the "currentConsumerPlan" value, the type of plan currently subscribed to. + // p6 is the "currentConsumerPlanTitle" value, the name of the plan currently subscribed to, shown to the user. + UserConsumerPlanConstructorFingerprint.resultOrThrow().mutableMethod.addInstructions( + 0, + """ + const-string p1, "high_tier" + new-instance p4, Ljava/util/ArrayList; + invoke-direct {p4}, Ljava/util/ArrayList;->()V + const-string p5, "go-plus" + const-string p6, "SoundCloud Go+" + """, + ) + + // Prevent verification of an HTTP header containing the user's current plan, which would contradict the previous patch. + InterceptFingerprint.resultOrThrow().let { result -> + val conditionIndex = result.scanResult.patternScanResult!!.endIndex + result.mutableMethod.addInstruction( + conditionIndex, + "return-object p1", + ) + } + } +} diff --git a/src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/FeatureConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/FeatureConstructorFingerprint.kt new file mode 100644 index 00000000..c6d93f49 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/FeatureConstructorFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.soundcloud.ad.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object FeatureConstructorFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + parameters = listOf("Ljava/lang/String;", "Z", "Ljava/util/List;"), + customFingerprint = { _, classDef -> + classDef.sourceFile == "Feature.kt" + }, +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/InterceptFingerprint.kt b/src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/InterceptFingerprint.kt new file mode 100644 index 00000000..d9f1e587 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/InterceptFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.soundcloud.ad.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal object InterceptFingerprint : MethodFingerprint( + returnType = "L", + accessFlags = AccessFlags.PUBLIC.value, + parameters = listOf("L"), + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + ), + strings = listOf("SC-Mob-UserPlan", "Configuration"), + customFingerprint = { _, classDef -> + classDef.sourceFile == "ApiUserPlanInterceptor.java" + }, +) diff --git a/src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/UserConsumerPlanConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/UserConsumerPlanConstructorFingerprint.kt new file mode 100644 index 00000000..0a238be5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/soundcloud/ad/fingerprints/UserConsumerPlanConstructorFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.soundcloud.ad.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object UserConsumerPlanConstructorFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + parameters = listOf("Ljava/lang/String;", "Z", "Ljava/lang/String;", "Ljava/util/List;", "Ljava/lang/String;", "Ljava/lang/String;"), + customFingerprint = { _, classDef -> + classDef.sourceFile == "UserConsumerPlan.kt" + }, +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/soundcloud/analytics/DisableTelemetryPatch.kt b/src/main/kotlin/app/revanced/patches/soundcloud/analytics/DisableTelemetryPatch.kt new file mode 100644 index 00000000..de4130b6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/soundcloud/analytics/DisableTelemetryPatch.kt @@ -0,0 +1,24 @@ +package app.revanced.patches.soundcloud.analytics + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotation.CompatiblePackage +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patches.soundcloud.analytics.fingerprints.CreateTrackingApiFingerprint +import app.revanced.util.resultOrThrow + +@Patch( + name = "Disable telemetry", + description = "Disables SoundCloud's telemetry system.", + compatiblePackages = [CompatiblePackage("com.soundcloud.android")], +) +@Suppress("unused") +object DisableTelemetryPatch : BytecodePatch( + setOf(CreateTrackingApiFingerprint), +) { + override fun execute(context: BytecodeContext) = + // Empty the "backend" argument to abort the initializer. + CreateTrackingApiFingerprint.resultOrThrow() + .mutableMethod.addInstruction(0, "const-string p1, \"\"") +} diff --git a/src/main/kotlin/app/revanced/patches/soundcloud/analytics/fingerprints/CreateTrackingApiFingerprint.kt b/src/main/kotlin/app/revanced/patches/soundcloud/analytics/fingerprints/CreateTrackingApiFingerprint.kt new file mode 100644 index 00000000..f305591d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/soundcloud/analytics/fingerprints/CreateTrackingApiFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.soundcloud.analytics.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object CreateTrackingApiFingerprint : MethodFingerprint( + returnType = "L", + accessFlags = AccessFlags.PUBLIC.value, + customFingerprint = { methodDef, classDef -> + classDef.sourceFile == "DefaultTrackingApiFactory.kt" && methodDef.name == "create" + }, +)