Skip to content

Commit

Permalink
feat(Tumblr): Add Disable Tumblr Live patch (#2987)
Browse files Browse the repository at this point in the history
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
  • Loading branch information
leumasme and oSumAtrIX authored Sep 20, 2023
1 parent fbaf3cc commit bf1f9dc
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package app.revanced.patches.tumblr.annoyances.notifications.patch
package app.revanced.patches.tumblr.annoyances.notifications

import app.revanced.extensions.exception
import app.revanced.patcher.data.BytecodeContext
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package app.revanced.patches.tumblr.annoyances.popups.patch
package app.revanced.patches.tumblr.annoyances.popups

import app.revanced.extensions.exception
import app.revanced.patcher.data.BytecodeContext
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package app.revanced.patches.tumblr.featureflags

import app.revanced.extensions.exception
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.patch.annotation.Patch
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

@Patch(description = "Forcibly set the value of A/B testing features of your choice.")
object OverrideFeatureFlagsPatch : BytecodePatch(
setOf(GetFeatureValueFingerprint)
) {
/**
* 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

override fun execute(context: BytecodeContext) = GetFeatureValueFingerprint.result?.let {
// The method we want to inject into does not have enough registers, so we inject a helper method
// and inject more instructions into it later, see addOverride.
// This is not in an integration since the unused variable would get compiled away and the method would
// get compiled to only have one register, which is not enough for our later injected instructions.
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 {
// This is the equivalent of
// String featureName = feature.toString()
// <inject more instructions here later>
// return null
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
"""
)
}.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
// This is equivalent to
// String forcedValue = getValueOverride(feature)
// if (forcedValue != null) return forcedValue
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.
// This is equivalent to
// if (featureName == {name}) return {value}
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
}
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,37 @@
package app.revanced.patches.tumblr.live

import app.revanced.extensions.exception
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.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.tumblr.featureflags.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.",
dependencies = [OverrideFeatureFlagsPatch::class],
compatiblePackages = [CompatiblePackage("com.tumblr")]
)
@Suppress("unused")
object DisableTumblrLivePatch : BytecodePatch(
setOf(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
}
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"))

0 comments on commit bf1f9dc

Please sign in to comment.