Skip to content

Commit a5ce75a

Browse files
committed
feat(duolingo/unlocksuper): Refactor user serialization fingerprints
- Replace object declarations with val for method fingerprints. - Simplify the fingerprint definitions for isUserSuper and userSerialization methods. - Update the UnlockDuolingoSuperPatch to use the new fingerprint structure. - Improve code readability and maintainability by reducing boilerplate. This refactor aims to streamline the fingerprinting process and enhance the clarity of the patching logic.
1 parent 36cb62b commit a5ce75a

File tree

3 files changed

+57
-85
lines changed

3 files changed

+57
-85
lines changed
Lines changed: 42 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,63 @@
11
package app.revanced.patches.duolingo.unlocksuper
22

3-
import app.revanced.patcher.data.BytecodeContext
43
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
54
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
6-
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
7-
import app.revanced.patcher.patch.BytecodePatch
5+
import app.revanced.patcher.extensions.InstructionExtensions.instructions
86
import app.revanced.patcher.patch.PatchException
9-
import app.revanced.patcher.patch.annotation.CompatiblePackage
10-
import app.revanced.patcher.patch.annotation.Patch
7+
import app.revanced.patcher.patch.bytecodePatch
118
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
12-
import app.revanced.patches.duolingo.unlocksuper.fingerprints.IsUserSuperMethodFingerprint
13-
import app.revanced.patches.duolingo.unlocksuper.fingerprints.UserSerializationMethodFingerprint
14-
import app.revanced.util.exception
9+
import app.revanced.patches.duolingo.unlocksuper.fingerprints.isUserSuperMethodFingerprint
10+
import app.revanced.patches.duolingo.unlocksuper.fingerprints.userSerializationMethodFingerprint
1511
import com.android.tools.smali.dexlib2.Opcode
1612
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction22c
1713
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
1814
import com.android.tools.smali.dexlib2.iface.reference.Reference
1915

20-
@Patch(
16+
@Suppress("unused")
17+
val unlockDuolingoSuperPatch = bytecodePatch(
2118
name = "Unlock Duolingo Super",
2219
description = "Unlocks Duolingo Super features by patching the user serialization to always enable Super status.",
23-
compatiblePackages = [CompatiblePackage("com.duolingo", ["6.51.4"])]
24-
)
25-
@Suppress("unused")
26-
object UnlockDuolingoSuperPatch : BytecodePatch(
27-
setOf(
28-
UserSerializationMethodFingerprint,
29-
IsUserSuperMethodFingerprint
30-
)
3120
) {
32-
/* First find the reference to the isUserSuper field, then patch the instruction that assigns it to false.
33-
* This strategy is used because the method that sets the isUserSuper field is difficult to fingerprint reliably.
34-
* Note: In version 6.51.4, this patch may not be needed if the premium patch handles all features.
35-
*/
36-
override fun execute(context: BytecodeContext) {
21+
compatibleWith("com.duolingo"("6.51.4"))
22+
23+
execute {
24+
/* First find the reference to the isUserSuper field, then patch the instruction that assigns it to false.
25+
* This strategy is used because the method that sets the isUserSuper field is difficult to fingerprint reliably.
26+
* Note: In version 6.51.4, this patch may not be needed if the premium patch handles all features.
27+
*/
28+
3729
// Find the reference to the isUserSuper field.
38-
val isUserSuperReference = IsUserSuperMethodFingerprint
39-
.result
40-
?.mutableMethod
41-
?.getInstructions()
42-
?.filterIsInstance<BuilderInstruction22c>()
43-
?.firstOrNull { it.opcode == Opcode.IGET_BOOLEAN }
30+
val isUserSuperReference = isUserSuperMethodFingerprint
31+
.method
32+
.instructions
33+
.filterIsInstance<BuilderInstruction22c>()
34+
.firstOrNull { it.opcode == Opcode.IGET_BOOLEAN }
4435
?.reference
45-
?: throw IsUserSuperMethodFingerprint.exception
36+
?: throw PatchException("Could not find isUserSuper field reference")
4637

4738
// Patch the instruction that assigns isUserSuper to true.
48-
UserSerializationMethodFingerprint
49-
.result
50-
?.mutableMethod
51-
?.apply {
52-
val assignIndex = indexOfReference(isUserSuperReference)
53-
val assignInstruction = getInstruction<TwoRegisterInstruction>(assignIndex)
39+
userSerializationMethodFingerprint.method.apply {
40+
val assignIndex = indexOfReference(isUserSuperReference)
41+
val assignInstruction = getInstruction<TwoRegisterInstruction>(assignIndex)
5442

55-
// add an instruction to force the value to `true`. ideally we'd replace the existing
56-
// instruction, but there's an `if` block above with different paths based on various
57-
// states (i.e. subscription vs super vs gold or whatever), and I don't think it's
58-
// worth removing the entire statement.
59-
addInstructions(
60-
assignIndex + 1,
61-
"""
62-
const/4 v${assignInstruction.registerA}, 0x1
63-
iput-boolean v${assignInstruction.registerA}, v0, $isUserSuperReference
64-
""".trimIndent()
65-
)
66-
}
67-
?: throw UserSerializationMethodFingerprint.exception
68-
}
69-
70-
private fun MutableMethod.indexOfReference(reference: Reference) = getInstructions()
71-
.indexOfFirst { it is BuilderInstruction22c && it.opcode == Opcode.IPUT_BOOLEAN && it.reference == reference }
72-
.let {
73-
if (it == -1) throw PatchException("Could not find index of instruction with supplied reference.")
74-
else it
43+
// add an instruction to force the value to `true`. ideally we'd replace the existing
44+
// instruction, but there's an `if` block above with different paths based on various
45+
// states (i.e. subscription vs super vs gold or whatever), and I don't think it's
46+
// worth removing the entire statement.
47+
addInstructions(
48+
assignIndex + 1,
49+
"""
50+
const/4 v${assignInstruction.registerA}, 0x1
51+
iput-boolean v${assignInstruction.registerA}, v0, $isUserSuperReference
52+
""".trimIndent()
53+
)
7554
}
55+
}
7656
}
57+
58+
private fun MutableMethod.indexOfReference(reference: Reference) = instructions
59+
.indexOfFirst { it is BuilderInstruction22c && it.opcode == Opcode.IPUT_BOOLEAN && it.reference == reference }
60+
.let {
61+
if (it == -1) throw PatchException("Could not find index of instruction with supplied reference.")
62+
else it
63+
}
Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
package app.revanced.patches.duolingo.unlocksuper.fingerprints
22

3-
import app.revanced.patcher.extensions.or
4-
import app.revanced.patcher.fingerprint.MethodFingerprint
3+
import app.revanced.patcher.fingerprint
54
import com.android.tools.smali.dexlib2.AccessFlags
65
import com.android.tools.smali.dexlib2.Opcode
76

8-
internal object IsUserSuperMethodFingerprint : MethodFingerprint(
9-
returnType = "Ljava/lang/Object",
10-
parameters = listOf(
11-
"Ljava/lang/Object",
12-
"Ljava/lang/Object",
13-
),
14-
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
15-
strings = listOf(
16-
"user",
17-
"heartsState",
18-
"superData",
19-
),
20-
opcodes = listOf(Opcode.IGET_BOOLEAN),
21-
)
7+
internal val isUserSuperMethodFingerprint = fingerprint {
8+
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
9+
returns("Ljava/lang/Object;")
10+
parameters("Ljava/lang/Object;", "Ljava/lang/Object;")
11+
strings("user", "heartsState", "superData")
12+
opcodes(Opcode.IGET_BOOLEAN)
13+
}
Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
package app.revanced.patches.duolingo.unlocksuper.fingerprints
22

3-
import app.revanced.patcher.extensions.or
4-
import app.revanced.patcher.fingerprint.MethodFingerprint
3+
import app.revanced.patcher.fingerprint
54
import com.android.tools.smali.dexlib2.AccessFlags
65
import com.android.tools.smali.dexlib2.Opcode
76

8-
internal object UserSerializationMethodFingerprint : MethodFingerprint(
9-
returnType = "V",
10-
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
11-
strings = listOf(
12-
"betaStatus",
13-
"subscriberLevel",
14-
),
15-
opcodes = listOf(
16-
Opcode.MOVE_FROM16,
17-
Opcode.IPUT_BOOLEAN,
18-
),
19-
)
7+
internal val userSerializationMethodFingerprint = fingerprint {
8+
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
9+
returns("V")
10+
strings("betaStatus", "subscriberLevel")
11+
opcodes(Opcode.MOVE_FROM16, Opcode.IPUT_BOOLEAN)
12+
}

0 commit comments

Comments
 (0)