mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-04-29 22:24:27 +02:00
fix(Spotify): Remove ads sections from home (#4722)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
parent
eaee621831
commit
0b9a5e7f89
@ -4,6 +4,7 @@ import static java.lang.Boolean.FALSE;
|
||||
import static java.lang.Boolean.TRUE;
|
||||
|
||||
import com.spotify.remoteconfig.internal.AccountAttribute;
|
||||
import com.spotify.home.evopage.homeapi.proto.Section;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -69,8 +70,13 @@ public final class UnlockPremiumPatch {
|
||||
new OverrideAttribute("tablet-free", FALSE, false)
|
||||
);
|
||||
|
||||
private static final List<Integer> REMOVED_HOME_SECTIONS = List.of(
|
||||
Section.VIDEO_BRAND_AD_FIELD_NUMBER,
|
||||
Section.IMAGE_BRAND_AD_FIELD_NUMBER
|
||||
);
|
||||
|
||||
/**
|
||||
* Injection point.
|
||||
* Override attributes injection point.
|
||||
*/
|
||||
public static void overrideAttribute(Map<String, AccountAttribute> attributes) {
|
||||
try {
|
||||
@ -78,7 +84,7 @@ public final class UnlockPremiumPatch {
|
||||
var attribute = attributes.get(override.key);
|
||||
if (attribute == null) {
|
||||
if (override.isExpected) {
|
||||
Logger.printException(() -> "''" + override.key + "' expected but not found");
|
||||
Logger.printException(() -> "'" + override.key + "' expected but not found");
|
||||
}
|
||||
} else {
|
||||
attribute.value_ = override.overrideValue;
|
||||
@ -88,4 +94,15 @@ public final class UnlockPremiumPatch {
|
||||
Logger.printException(() -> "overrideAttribute failure", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove ads sections from home injection point.
|
||||
*/
|
||||
public static void removeHomeSections(List<Section> sections) {
|
||||
try {
|
||||
sections.removeIf(section -> REMOVED_HOME_SECTIONS.contains(section.featureTypeCase_));
|
||||
} catch (Exception ex) {
|
||||
Logger.printException(() -> "Remove home sections failure", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
package com.spotify.home.evopage.homeapi.proto;
|
||||
|
||||
public final class Section {
|
||||
public static final int VIDEO_BRAND_AD_FIELD_NUMBER = 20;
|
||||
public static final int IMAGE_BRAND_AD_FIELD_NUMBER = 21;
|
||||
public int featureTypeCase_;
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
package app.revanced.patches.spotify.misc
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val accountAttributeFingerprint = fingerprint {
|
||||
custom { _, c -> c.endsWith("internal/AccountAttribute;") }
|
||||
custom { _, classDef -> classDef.endsWith("internal/AccountAttribute;") }
|
||||
}
|
||||
|
||||
internal val productStateProtoFingerprint = fingerprint {
|
||||
returns("Ljava/util/Map;")
|
||||
custom { _, classDef ->
|
||||
classDef.endsWith("ProductStateProto;")
|
||||
}
|
||||
custom { _, classDef -> classDef.endsWith("ProductStateProto;") }
|
||||
}
|
||||
|
||||
internal val buildQueryParametersFingerprint = fingerprint {
|
||||
@ -21,3 +21,17 @@ internal val contextMenuExperimentsFingerprint = fingerprint {
|
||||
parameters("L")
|
||||
strings("remove_ads_upsell_enabled")
|
||||
}
|
||||
|
||||
internal val homeSectionFingerprint = fingerprint {
|
||||
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
|
||||
}
|
||||
|
||||
internal val protobufListsFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
custom { method, _ -> method.name == "emptyProtobufList" }
|
||||
}
|
||||
|
||||
internal val homeStructureFingerprint = fingerprint {
|
||||
opcodes(Opcode.IGET_OBJECT, Opcode.RETURN_OBJECT)
|
||||
custom { _, classDef -> classDef.endsWith("homeapi/proto/HomeStructure;") }
|
||||
}
|
||||
|
@ -2,11 +2,18 @@ package app.revanced.patches.spotify.misc
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.*
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/UnlockPremiumPatch;"
|
||||
|
||||
@ -27,23 +34,75 @@ val unlockPremiumPatch = bytecodePatch(
|
||||
}
|
||||
|
||||
// Override the attributes map in the getter method.
|
||||
val attributesMapRegister = 0
|
||||
val instantiateUnmodifiableMapIndex = 1
|
||||
productStateProtoFingerprint.method.addInstruction(
|
||||
instantiateUnmodifiableMapIndex,
|
||||
"invoke-static { v$attributesMapRegister }," +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->overrideAttribute(Ljava/util/Map;)V",
|
||||
)
|
||||
with(productStateProtoFingerprint.method) {
|
||||
val getAttributesMapIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
|
||||
val attributesMapRegister = getInstruction<TwoRegisterInstruction>(getAttributesMapIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
getAttributesMapIndex + 1,
|
||||
"invoke-static { v$attributesMapRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->overrideAttribute(Ljava/util/Map;)V"
|
||||
)
|
||||
}
|
||||
|
||||
// Add the query parameter trackRows to show popular tracks in the artist page.
|
||||
val addQueryParameterIndex = buildQueryParametersFingerprint.stringMatches!!.first().index - 1
|
||||
buildQueryParametersFingerprint.method.replaceInstruction(addQueryParameterIndex, "nop")
|
||||
with(buildQueryParametersFingerprint) {
|
||||
val addQueryParameterConditionIndex = method.indexOfFirstInstructionReversedOrThrow(
|
||||
stringMatches!!.first().index, Opcode.IF_EQZ
|
||||
)
|
||||
method.replaceInstruction(addQueryParameterConditionIndex, "nop")
|
||||
}
|
||||
|
||||
// Disable the "Spotify Premium" upsell experiment in context menus.
|
||||
with(contextMenuExperimentsFingerprint) {
|
||||
val moveIsEnabledIndex = stringMatches!!.first().index + 2
|
||||
val moveIsEnabledIndex = method.indexOfFirstInstructionOrThrow(
|
||||
stringMatches!!.first().index, Opcode.MOVE_RESULT
|
||||
)
|
||||
val isUpsellEnabledRegister = method.getInstruction<OneRegisterInstruction>(moveIsEnabledIndex).registerA
|
||||
method.replaceInstruction(moveIsEnabledIndex, "const/4 v$isUpsellEnabledRegister, 0")
|
||||
}
|
||||
|
||||
// Make featureTypeCase_ accessible so we can check the home section type in the extension.
|
||||
homeSectionFingerprint.classDef.fields.first { it.name == "featureTypeCase_" }.apply {
|
||||
accessFlags = accessFlags.or(AccessFlags.PUBLIC.value).and(AccessFlags.PRIVATE.value.inv())
|
||||
}
|
||||
|
||||
val protobufListClassName = with(protobufListsFingerprint.originalMethod) {
|
||||
val emptyProtobufListGetIndex = indexOfFirstInstructionOrThrow(Opcode.SGET_OBJECT)
|
||||
getInstruction(emptyProtobufListGetIndex).getReference<FieldReference>()!!.definingClass
|
||||
}
|
||||
|
||||
val protobufListRemoveFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "remove" && classDef.type == protobufListClassName
|
||||
}
|
||||
}
|
||||
|
||||
// Need to allow mutation of the list so the home ads sections can be removed.
|
||||
// Protobuffer list has an 'isMutable' boolean parameter that sets the mutability.
|
||||
// Forcing that always on breaks unrelated code in strange ways.
|
||||
// Instead, remove the method call that checks if the list is unmodifiable.
|
||||
with(protobufListRemoveFingerprint.method) {
|
||||
val invokeThrowUnmodifiableIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<MethodReference>()
|
||||
opcode == Opcode.INVOKE_VIRTUAL &&
|
||||
reference?.returnType == "V" && reference.parameterTypes.isEmpty()
|
||||
}
|
||||
|
||||
// Remove the method call that throws an exception if the list is not mutable.
|
||||
removeInstruction(invokeThrowUnmodifiableIndex)
|
||||
}
|
||||
|
||||
// Remove ads sections from home.
|
||||
with(homeStructureFingerprint.method) {
|
||||
val getSectionsIndex = indexOfFirstInstructionOrThrow(Opcode.IGET_OBJECT)
|
||||
val sectionsRegister = getInstruction<TwoRegisterInstruction>(getSectionsIndex).registerA
|
||||
|
||||
addInstruction(
|
||||
getSectionsIndex + 1,
|
||||
"invoke-static { v$sectionsRegister }, " +
|
||||
"$EXTENSION_CLASS_DESCRIPTOR->removeHomeSections(Ljava/util/List;)V"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,15 @@ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude:
|
||||
|
||||
// All registers used by an instruction.
|
||||
fun Instruction.getRegistersUsed() = when (this) {
|
||||
is FiveRegisterInstruction -> listOf(registerC, registerD, registerE, registerF, registerG)
|
||||
is FiveRegisterInstruction -> {
|
||||
when (registerCount) {
|
||||
1 -> listOf(registerC)
|
||||
2 -> listOf(registerC, registerD)
|
||||
3 -> listOf(registerC, registerD, registerE)
|
||||
4 -> listOf(registerC, registerD, registerE, registerF)
|
||||
else -> listOf(registerC, registerD, registerE, registerF, registerG)
|
||||
}
|
||||
}
|
||||
is ThreeRegisterInstruction -> listOf(registerA, registerB, registerC)
|
||||
is TwoRegisterInstruction -> listOf(registerA, registerB)
|
||||
is OneRegisterInstruction -> listOf(registerA)
|
||||
@ -62,15 +70,15 @@ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude:
|
||||
}
|
||||
|
||||
// Register that is written to by an instruction.
|
||||
fun Instruction.getRegisterWritten() = when (this) {
|
||||
is ThreeRegisterInstruction -> registerA
|
||||
is TwoRegisterInstruction -> registerA
|
||||
is OneRegisterInstruction -> registerA
|
||||
else -> throw IllegalStateException("Not a write instruction: $this")
|
||||
fun Instruction.getWriteRegister() : Int {
|
||||
// Two and three register instructions extend OneRegisterInstruction.
|
||||
if (this is OneRegisterInstruction) return registerA
|
||||
throw IllegalStateException("Not a write instruction: $this")
|
||||
}
|
||||
|
||||
val writeOpcodes = EnumSet.of(
|
||||
ARRAY_LENGTH,
|
||||
INSTANCE_OF,
|
||||
NEW_INSTANCE, NEW_ARRAY,
|
||||
MOVE, MOVE_FROM16, MOVE_16, MOVE_WIDE, MOVE_WIDE_FROM16, MOVE_WIDE_16, MOVE_OBJECT,
|
||||
MOVE_OBJECT_FROM16, MOVE_OBJECT_16, MOVE_RESULT, MOVE_RESULT_WIDE, MOVE_RESULT_OBJECT, MOVE_EXCEPTION,
|
||||
@ -140,7 +148,7 @@ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude:
|
||||
return freeRegister
|
||||
}
|
||||
if (bestFreeRegisterFound != null) {
|
||||
return bestFreeRegisterFound;
|
||||
return bestFreeRegisterFound
|
||||
}
|
||||
|
||||
// Somehow every method register was read from before any register was wrote to.
|
||||
@ -151,14 +159,14 @@ internal fun Method.findFreeRegister(startIndex: Int, vararg registersToExclude:
|
||||
|
||||
if (instruction.opcode in branchOpcodes) {
|
||||
if (bestFreeRegisterFound != null) {
|
||||
return bestFreeRegisterFound;
|
||||
return bestFreeRegisterFound
|
||||
}
|
||||
// This method is simple and does not follow branching.
|
||||
throw IllegalArgumentException("Encountered a branch statement before a free register could be found")
|
||||
}
|
||||
|
||||
if (instruction.opcode in writeOpcodes) {
|
||||
val writeRegister = instruction.getRegisterWritten()
|
||||
val writeRegister = instruction.getWriteRegister()
|
||||
|
||||
if (writeRegister !in usedRegisters) {
|
||||
// Verify the register is only used for write and not also as a parameter.
|
||||
|
Loading…
x
Reference in New Issue
Block a user