From 628d18489c31a468400f856ff00b2db66f3f18b0 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 4 Apr 2025 09:32:40 -0300 Subject: [PATCH] fix(Spotify): Remove ads sections from home (#4722) Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> --- .../home/evopage/homeapi/proto/Section.java | 7 +++++ .../patches/spotify/misc/Fingerprints.kt | 22 +++++++++++++--- .../kotlin/app/revanced/util/BytecodeUtils.kt | 26 ++++++++++++------- 3 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 extensions/spotify/stub/src/main/java/com/spotify/home/evopage/homeapi/proto/Section.java diff --git a/extensions/spotify/stub/src/main/java/com/spotify/home/evopage/homeapi/proto/Section.java b/extensions/spotify/stub/src/main/java/com/spotify/home/evopage/homeapi/proto/Section.java new file mode 100644 index 000000000..cc7da5f71 --- /dev/null +++ b/extensions/spotify/stub/src/main/java/com/spotify/home/evopage/homeapi/proto/Section.java @@ -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_; +} diff --git a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/Fingerprints.kt index 876d390f2..b23f405f4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/Fingerprints.kt @@ -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;") } +} diff --git a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index c32a0c072..4d3826865 100644 --- a/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/patches/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -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.