From d0a8599f76ce653e5d7c98069ad3c58b9ab9c5eb Mon Sep 17 00:00:00 2001 From: Joshua May Date: Sat, 24 Aug 2024 05:44:35 -0700 Subject: [PATCH] feat(Duolingo): Add `Disable ads` and `Enable debug menu` patch (#3422) Co-authored-by: oSumAtrIX Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> --- api/revanced-patches.api | 12 ++++++ .../patches/duolingo/ad/DisableAdsPatch.kt | 41 +++++++++++++++++++ ...izeMonetizationDebugSettingsFingerprint.kt | 21 ++++++++++ .../duolingo/debug/EnableDebugMenuPatch.kt | 35 ++++++++++++++++ ...nitializeBuildConfigProviderFingerprint.kt | 25 +++++++++++ 5 files changed, 134 insertions(+) create mode 100644 src/main/kotlin/app/revanced/patches/duolingo/ad/DisableAdsPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/duolingo/ad/fingerprints/InitializeMonetizationDebugSettingsFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/duolingo/debug/EnableDebugMenuPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/duolingo/debug/fingerprints/InitializeBuildConfigProviderFingerprint.kt diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 7b5140448..27fedbe32 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -243,6 +243,18 @@ public final class app/revanced/patches/cieid/restrictions/root/BypassRootChecks public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } +public final class app/revanced/patches/duolingo/ad/DisableAdsPatch : app/revanced/patcher/patch/BytecodePatch { + public static final field INSTANCE Lapp/revanced/patches/duolingo/ad/DisableAdsPatch; + 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/duolingo/debug/EnableDebugMenuPatch : app/revanced/patcher/patch/BytecodePatch { + public static final field INSTANCE Lapp/revanced/patches/duolingo/debug/EnableDebugMenuPatch; + 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/facebook/ads/story/HideStoryAdsPatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/facebook/ads/story/HideStoryAdsPatch; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V diff --git a/src/main/kotlin/app/revanced/patches/duolingo/ad/DisableAdsPatch.kt b/src/main/kotlin/app/revanced/patches/duolingo/ad/DisableAdsPatch.kt new file mode 100644 index 000000000..c192f35aa --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/duolingo/ad/DisableAdsPatch.kt @@ -0,0 +1,41 @@ +package app.revanced.patches.duolingo.ad + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +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.patches.duolingo.ad.fingerprints.InitializeMonetizationDebugSettingsFingerprint +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction + +@Patch( + name = "Disable ads", + compatiblePackages = [CompatiblePackage("com.duolingo")] +) +@Suppress("unused") +object DisableAdsPatch : BytecodePatch( + setOf(InitializeMonetizationDebugSettingsFingerprint) +) { + override fun execute(context: BytecodeContext) { + // Couple approaches to remove ads exist: + // + // MonetizationDebugSettings has a boolean value for "disableAds". + // OnboardingState has a getter to check if the user has any "adFreeSessions". + // SharedPreferences has a debug boolean value with key "disable_ads", which maps to "DebugCategory.DISABLE_ADS". + // + // MonetizationDebugSettings seems to be the most general setting to work fine. + InitializeMonetizationDebugSettingsFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val insertIndex = it.scanResult.patternScanResult!!.startIndex + val register = getInstruction(insertIndex).registerA + + addInstructions( + insertIndex, + "const/4 v$register, 0x1" + ) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/duolingo/ad/fingerprints/InitializeMonetizationDebugSettingsFingerprint.kt b/src/main/kotlin/app/revanced/patches/duolingo/ad/fingerprints/InitializeMonetizationDebugSettingsFingerprint.kt new file mode 100644 index 000000000..62247e115 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/duolingo/ad/fingerprints/InitializeMonetizationDebugSettingsFingerprint.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.duolingo.ad.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal object InitializeMonetizationDebugSettingsFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + parameters = listOf( + "Z", // disableAds + "Z", // useDebugBilling + "Z", // showManageSubscriptions + "Z", // alwaysShowSuperAds + "Lcom/duolingo/debug/FamilyQuestOverride;", + ), + opcodes = listOf( + Opcode.IPUT_BOOLEAN + ) +) diff --git a/src/main/kotlin/app/revanced/patches/duolingo/debug/EnableDebugMenuPatch.kt b/src/main/kotlin/app/revanced/patches/duolingo/debug/EnableDebugMenuPatch.kt new file mode 100644 index 000000000..8b1a66d60 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/duolingo/debug/EnableDebugMenuPatch.kt @@ -0,0 +1,35 @@ +package app.revanced.patches.duolingo.debug + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +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.patches.duolingo.debug.fingerprints.InitializeBuildConfigProviderFingerprint +import app.revanced.util.resultOrThrow +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction + +@Patch( + name = "Enable debug menu", + compatiblePackages = [CompatiblePackage("com.duolingo", ["5.158.4"])], + use = false +) +@Suppress("unused") +object EnableDebugMenuPatch : BytecodePatch( + setOf(InitializeBuildConfigProviderFingerprint) +) { + override fun execute(context: BytecodeContext) { + InitializeBuildConfigProviderFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val insertIndex = it.scanResult.patternScanResult!!.startIndex + val register = getInstruction(insertIndex).registerA + + addInstructions( + insertIndex, + "const/4 v$register, 0x1" + ) + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/duolingo/debug/fingerprints/InitializeBuildConfigProviderFingerprint.kt b/src/main/kotlin/app/revanced/patches/duolingo/debug/fingerprints/InitializeBuildConfigProviderFingerprint.kt new file mode 100644 index 000000000..edd3d9bd9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/duolingo/debug/fingerprints/InitializeBuildConfigProviderFingerprint.kt @@ -0,0 +1,25 @@ +package app.revanced.patches.duolingo.debug.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +/** + * The `BuildConfigProvider` class has two booleans: + * + * - `isChina`: (usually) compares "play" with "china"...except for builds in China + * - `isDebug`: compares "release" with "debug" <-- we want to force this to `true` + */ +internal object InitializeBuildConfigProviderFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + strings = listOf( + "debug", + "release", + "china", + ), + opcodes = listOf( + Opcode.IPUT_BOOLEAN + ) +)