Skip to content

Commit 4e323aa

Browse files
notjoshoSumAtrIXLisoUseInAIKyrios
authored
feat(Duolingo): Add Disable ads and Enable debug menu patch (#3422)
Co-authored-by: oSumAtrIX <[email protected]> Co-authored-by: LisoUseInAIKyrios <[email protected]>
1 parent c1cee28 commit 4e323aa

File tree

5 files changed

+134
-0
lines changed

5 files changed

+134
-0
lines changed

api/revanced-patches.api

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,18 @@ public final class app/revanced/patches/cieid/restrictions/root/BypassRootChecks
243243
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
244244
}
245245

246+
public final class app/revanced/patches/duolingo/ad/DisableAdsPatch : app/revanced/patcher/patch/BytecodePatch {
247+
public static final field INSTANCE Lapp/revanced/patches/duolingo/ad/DisableAdsPatch;
248+
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
249+
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
250+
}
251+
252+
public final class app/revanced/patches/duolingo/debug/EnableDebugMenuPatch : app/revanced/patcher/patch/BytecodePatch {
253+
public static final field INSTANCE Lapp/revanced/patches/duolingo/debug/EnableDebugMenuPatch;
254+
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
255+
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
256+
}
257+
246258
public final class app/revanced/patches/facebook/ads/story/HideStoryAdsPatch : app/revanced/patcher/patch/BytecodePatch {
247259
public static final field INSTANCE Lapp/revanced/patches/facebook/ads/story/HideStoryAdsPatch;
248260
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package app.revanced.patches.duolingo.ad
2+
3+
import app.revanced.patcher.data.BytecodeContext
4+
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
5+
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
6+
import app.revanced.patcher.patch.BytecodePatch
7+
import app.revanced.patcher.patch.annotation.CompatiblePackage
8+
import app.revanced.patcher.patch.annotation.Patch
9+
import app.revanced.patches.duolingo.ad.fingerprints.InitializeMonetizationDebugSettingsFingerprint
10+
import app.revanced.util.resultOrThrow
11+
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
12+
13+
@Patch(
14+
name = "Disable ads",
15+
compatiblePackages = [CompatiblePackage("com.duolingo")]
16+
)
17+
@Suppress("unused")
18+
object DisableAdsPatch : BytecodePatch(
19+
setOf(InitializeMonetizationDebugSettingsFingerprint)
20+
) {
21+
override fun execute(context: BytecodeContext) {
22+
// Couple approaches to remove ads exist:
23+
//
24+
// MonetizationDebugSettings has a boolean value for "disableAds".
25+
// OnboardingState has a getter to check if the user has any "adFreeSessions".
26+
// SharedPreferences has a debug boolean value with key "disable_ads", which maps to "DebugCategory.DISABLE_ADS".
27+
//
28+
// MonetizationDebugSettings seems to be the most general setting to work fine.
29+
InitializeMonetizationDebugSettingsFingerprint.resultOrThrow().let {
30+
it.mutableMethod.apply {
31+
val insertIndex = it.scanResult.patternScanResult!!.startIndex
32+
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
33+
34+
addInstructions(
35+
insertIndex,
36+
"const/4 v$register, 0x1"
37+
)
38+
}
39+
}
40+
}
41+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package app.revanced.patches.duolingo.ad.fingerprints
2+
3+
import app.revanced.patcher.extensions.or
4+
import app.revanced.patcher.fingerprint.MethodFingerprint
5+
import com.android.tools.smali.dexlib2.AccessFlags
6+
import com.android.tools.smali.dexlib2.Opcode
7+
8+
internal object InitializeMonetizationDebugSettingsFingerprint : MethodFingerprint(
9+
returnType = "V",
10+
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
11+
parameters = listOf(
12+
"Z", // disableAds
13+
"Z", // useDebugBilling
14+
"Z", // showManageSubscriptions
15+
"Z", // alwaysShowSuperAds
16+
"Lcom/duolingo/debug/FamilyQuestOverride;",
17+
),
18+
opcodes = listOf(
19+
Opcode.IPUT_BOOLEAN
20+
)
21+
)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package app.revanced.patches.duolingo.debug
2+
3+
import app.revanced.patcher.data.BytecodeContext
4+
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
5+
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
6+
import app.revanced.patcher.patch.BytecodePatch
7+
import app.revanced.patcher.patch.annotation.CompatiblePackage
8+
import app.revanced.patcher.patch.annotation.Patch
9+
import app.revanced.patches.duolingo.debug.fingerprints.InitializeBuildConfigProviderFingerprint
10+
import app.revanced.util.resultOrThrow
11+
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
12+
13+
@Patch(
14+
name = "Enable debug menu",
15+
compatiblePackages = [CompatiblePackage("com.duolingo", ["5.158.4"])],
16+
use = false
17+
)
18+
@Suppress("unused")
19+
object EnableDebugMenuPatch : BytecodePatch(
20+
setOf(InitializeBuildConfigProviderFingerprint)
21+
) {
22+
override fun execute(context: BytecodeContext) {
23+
InitializeBuildConfigProviderFingerprint.resultOrThrow().let {
24+
it.mutableMethod.apply {
25+
val insertIndex = it.scanResult.patternScanResult!!.startIndex
26+
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
27+
28+
addInstructions(
29+
insertIndex,
30+
"const/4 v$register, 0x1"
31+
)
32+
}
33+
}
34+
}
35+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package app.revanced.patches.duolingo.debug.fingerprints
2+
3+
import app.revanced.patcher.extensions.or
4+
import app.revanced.patcher.fingerprint.MethodFingerprint
5+
import com.android.tools.smali.dexlib2.AccessFlags
6+
import com.android.tools.smali.dexlib2.Opcode
7+
8+
/**
9+
* The `BuildConfigProvider` class has two booleans:
10+
*
11+
* - `isChina`: (usually) compares "play" with "china"...except for builds in China
12+
* - `isDebug`: compares "release" with "debug" <-- we want to force this to `true`
13+
*/
14+
internal object InitializeBuildConfigProviderFingerprint : MethodFingerprint(
15+
returnType = "V",
16+
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
17+
strings = listOf(
18+
"debug",
19+
"release",
20+
"china",
21+
),
22+
opcodes = listOf(
23+
Opcode.IPUT_BOOLEAN
24+
)
25+
)

0 commit comments

Comments
 (0)