feat(Spotify): Add Unlock premium patch (#4644)

Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Brosssh <tiabroch@gmail.com>
This commit is contained in:
oSumAtrIX 2025-03-26 04:24:47 +01:00 committed by GitHub
parent bae80204c4
commit f048c50e56
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 125 additions and 52 deletions

View File

@ -0,0 +1,3 @@
dependencies {
compileOnly(project(":extensions:spotify:stub"))
}

View File

@ -0,0 +1 @@
<manifest/>

View File

@ -0,0 +1,37 @@
package app.revanced.extension.spotify.misc;
import com.spotify.remoteconfig.internal.AccountAttribute;
import java.util.Map;
import java.util.Objects;
/**
* @noinspection unused
*/
public final class UnlockPremiumPatch {
private static final Map<String, Object> OVERRIDES = Map.of(
// Disables player and app ads.
"ads", false,
// Works along on-demand, allows playing any song without restriction.
"player-license", "premium",
// Disables shuffle being initially enabled when first playing a playlist.
"shuffle", false,
// Allows playing any song on-demand, without a shuffled order.
"on-demand", true,
// Allows adding songs to queue and removes the smart shuffle mode restriction,
// allowing to pick any of the other modes.
"pick-and-shuffle", false,
// Disables shuffle-mode streaming-rule, which forces songs to be played shuffled
// and breaks the player when other patches are applied.
"streaming-rules", "",
// Enables premium UI in settings and removes the premium button in the nav-bar.
"nft-disabled", "1"
);
public static void overrideAttribute(Map<String, AccountAttribute> attributes) {
for (var entry : OVERRIDES.entrySet()) {
var attribute = Objects.requireNonNull(attributes.get(entry.getKey()));
attribute.value_ = entry.getValue();
}
}
}

View File

@ -0,0 +1,17 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
}
android {
namespace = "app.revanced.extension"
compileSdk = 34
defaultConfig {
minSdk = 26
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
}

View File

@ -0,0 +1 @@
<manifest/>

View File

@ -0,0 +1,5 @@
package com.spotify.remoteconfig.internal;
public final class AccountAttribute {
public Object value_;
}

View File

@ -816,6 +816,10 @@ public final class app/revanced/patches/spotify/lite/ondemand/OnDemandPatchKt {
public static final fun getOnDemandPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/UnlockPremiumPatchKt {
public static final fun getUnlockPremiumPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/spotify/misc/fix/SpoofSignaturePatchKt {
public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}

View File

@ -0,0 +1,18 @@
package app.revanced.patches.spotify.misc
import app.revanced.patcher.fingerprint
internal val accountAttributeFingerprint = fingerprint {
custom { _, c -> c.endsWith("internal/AccountAttribute;") }
}
internal val productStateProtoFingerprint = fingerprint {
returns("Ljava/util/Map;")
custom { _, classDef ->
classDef.endsWith("ProductStateProto;")
}
}
internal val buildQueryParametersFingerprint = fingerprint {
strings("trackRows", "device_type:tablet")
}

View File

@ -0,0 +1,36 @@
package app.revanced.patches.spotify.misc
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import com.android.tools.smali.dexlib2.AccessFlags
@Suppress("unused")
val unlockPremiumPatch = bytecodePatch(
name = "Unlock Spotify Premium",
description = "Unlock Spotify Premium features. Server-sided features like downloading songs are still locked.",
) {
compatibleWith("com.spotify.music")
extendWith("extensions/spotify.rve")
execute {
// Make _value accessible so that it can be overridden in the extension.
accountAttributeFingerprint.classDef.fields.first { it.name == "value_" }.apply {
accessFlags = accessFlags.or(AccessFlags.PUBLIC.value).and(AccessFlags.PRIVATE.value.inv())
}
// Override the attributes map in the getter method.
val attributesMapRegister = 0
val instantiateUnmodifiableMapIndex = 1
productStateProtoFingerprint.method.addInstruction(
instantiateUnmodifiableMapIndex,
"invoke-static { v$attributesMapRegister }," +
"Lapp/revanced/extension/spotify/misc/UnlockPremiumPatch;->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")
}
}

View File

@ -1,11 +0,0 @@
package app.revanced.patches.spotify.navbar
import app.revanced.patcher.fingerprint
import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags
internal val addNavBarItemFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
returns("V")
literal { showBottomNavigationItemsTextId }
}

View File

@ -1,50 +1,12 @@
package app.revanced.patches.spotify.navbar
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.shared.misc.mapping.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings
internal var showBottomNavigationItemsTextId = -1L
private set
internal var premiumTabId = -1L
private set
private val premiumNavbarTabResourcePatch = resourcePatch {
dependsOn(resourceMappingPatch)
execute {
premiumTabId = resourceMappings["id", "premium_tab"]
showBottomNavigationItemsTextId = resourceMappings[
"bool",
"show_bottom_navigation_items_text",
]
}
}
import app.revanced.patches.spotify.misc.unlockPremiumPatch
@Deprecated("Superseded by unlockPremiumPatch", ReplaceWith("unlockPremiumPatch"))
@Suppress("unused")
val premiumNavbarTabPatch = bytecodePatch(
name = "Premium navbar tab",
description = "Hides the premium tab from the navigation bar.",
) {
dependsOn(premiumNavbarTabResourcePatch)
compatibleWith("com.spotify.music")
// If the navigation bar item is the premium tab, do not add it.
execute {
addNavBarItemFingerprint.method.addInstructions(
0,
"""
const v1, $premiumTabId
if-ne p5, v1, :continue
return-void
:continue
nop
""",
)
}
dependsOn(unlockPremiumPatch)
}