Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Tumblr): Add Disable Tumblr Live patch #2987

Merged
merged 5 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package app.revanced.patches.tumblr.featureflags.fingerprints

import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode

// This fingerprint targets the method to get the value of a Feature in the class "com.tumblr.configuration.Feature".
// Features seem to be Tumblr's A/B testing program.
// Feature states are loaded from the server in the "api-http2.tumblr.com/v2/config" request on (first) startup.
// A lot of features are returned there, but most of them do not seem to do anything (anymore).
// They were likely removed in newer App versions to always be on, but are still returned
// as enabled for old App versions.
// Some features seem to be very old and never removed, though, such as Google Login.
// The startIndex of the opcode pattern is at the start of the function after the arg null check.
// we want to insert our instructions there.
object GetFeatureValueFingerprint : MethodFingerprint(
strings = listOf("feature"),
opcodes = listOf(
Opcode.IF_EQZ,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT
),
customFingerprint = { method, _ -> method.definingClass == "Lcom/tumblr/configuration/Configuration;" }
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package app.revanced.patches.tumblr.featureflags.patch

import app.revanced.extensions.exception
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Package
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.or
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patches.tumblr.featureflags.fingerprints.GetFeatureValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter

@Name("Override feature flags")
@Description("Forcibly set the value of A/B testing features of your choice.")
@Compatibility([Package("com.tumblr")])
class OverrideFeatureFlagsPatch : BytecodePatch(
listOf(GetFeatureValueFingerprint)
) {
override fun execute(context: BytecodeContext) = GetFeatureValueFingerprint.result?.let {
// The method we want to inject into does not have enough registers,
// so instead of dealing with increasing the register count, we add a
// helper method "getValueOverride" to the class and pass it the feature object.
// If the helper returns null, we let the function run normally, otherwise we return the helper result value.
val helperMethod = ImmutableMethod(
it.method.definingClass,
"getValueOverride",
listOf(ImmutableMethodParameter("Lcom/tumblr/configuration/Feature;", null, "feature")),
"Ljava/lang/String;",
AccessFlags.PUBLIC or AccessFlags.FINAL,
null,
null,
MutableMethodImplementation(4)
).toMutable().apply {
// At the start of the helper, we get the String representation of the enum once.
// At the end of the helper, we return null.
// Between these two, we will later insert the checks & returns for the overrides
addInstructions(
0,
"""
# toString() the enum value
invoke-virtual {p1}, Lcom/tumblr/configuration/Feature;->toString()Ljava/lang/String;
move-result-object v0

# !!! If you add more instructions above this line, update helperInsertIndex below!
# Here we will insert one compare & return for every registered Feature override
# This is done below in the addOverride function

# If none of the overrides returned a value, we should return null
const/4 v0, 0x0
return-object v0
"""
)
leumasme marked this conversation as resolved.
Show resolved Hide resolved
}.also { helperMethod ->
it.mutableClass.methods.add(helperMethod)
}


// Here we actually insert the hook to call our helper method and return its value if it returns not null
val getFeatureIndex = it.scanResult.patternScanResult!!.startIndex
it.mutableMethod.addInstructionsWithLabels(
getFeatureIndex,
"""
# Call the Helper Method with the Feature
invoke-virtual {p0, p1}, Lcom/tumblr/configuration/Configuration;->getValueOverride(Lcom/tumblr/configuration/Feature;)Ljava/lang/String;
move-result-object v0
# If it returned null, skip
if-eqz v0, :is_null
# If it didnt return null, return that string
return-object v0

# If our override helper returned null, we let the function continue normally
:is_null
nop
"""
)

val helperInsertIndex = 2
addOverride = { name, value ->
// For every added override, we add a few instructions in the middle of the helper method
// to check if the feature is the one we want and return the override value if it is.
helperMethod.addInstructionsWithLabels(
helperInsertIndex,
"""
# v0 is still the string name of the currently checked feature from above
# Compare the current string with the override string
const-string v1, "$name"
invoke-virtual {v0, v1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z
move-result v1
# If the current string is the one we want to override, we return the override value
if-eqz v1, :no_override
const-string v1, "$value"
return-object v1
# Else we just continue...
:no_override
nop
"""
)
}
} ?: throw GetFeatureValueFingerprint.exception

companion object {
/**
* Override a feature flag with a value.
*
* @param name The name of the feature flag to override.
* @param value The value to override the feature flag with.
*/
@Suppress("KDocUnresolvedReference")
internal lateinit var addOverride: (name: String, value: String) -> Unit private set
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package app.revanced.patches.tumblr.live.fingerprints

import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint

// This works identically to the Tumblr AdWaterfallFingerprint, see comments there
object LiveMarqueeFingerprint : MethodFingerprint(strings = listOf("live_marquee"))
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package app.revanced.patches.tumblr.live.patch

import app.revanced.extensions.exception
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Package
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patches.tumblr.featureflags.patch.OverrideFeatureFlagsPatch
import app.revanced.patches.tumblr.live.fingerprints.LiveMarqueeFingerprint
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction

@Patch
@Name("Disable Tumblr Live")
@Description("Disable the Tumblr Live tab button and dashboard carousel.")
@DependsOn([OverrideFeatureFlagsPatch::class])
@Compatibility([Package("com.tumblr")])
class DisableTumblrLivePatch : BytecodePatch(
listOf(LiveMarqueeFingerprint)
) {
override fun execute(context: BytecodeContext) = LiveMarqueeFingerprint.result?.let {
it.scanResult.stringsScanResult!!.matches.forEach { match ->
// Replace the string constant "live_marquee"
// with a dummy so the app doesn't recognize this type of element in the Dashboard and skips it
it.mutableMethod.apply {
val stringRegister = getInstruction<OneRegisterInstruction>(match.index).registerA
replaceInstruction(match.index, "const-string v$stringRegister, \"dummy2\"")
}

}

// We hide the Tab button for Tumblr Live by forcing the feature flag to false
OverrideFeatureFlagsPatch.addOverride("liveStreaming", "false")
} ?: throw LiveMarqueeFingerprint.exception
}