mirror of
https://github.com/revanced/revanced-patches.git
synced 2025-05-01 23:24:29 +02:00
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:
parent
bae80204c4
commit
f048c50e56
3
extensions/spotify/build.gradle.kts
Normal file
3
extensions/spotify/build.gradle.kts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
dependencies {
|
||||||
|
compileOnly(project(":extensions:spotify:stub"))
|
||||||
|
}
|
1
extensions/spotify/src/main/AndroidManifest.xml
Normal file
1
extensions/spotify/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
extensions/spotify/stub/build.gradle.kts
Normal file
17
extensions/spotify/stub/build.gradle.kts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
1
extensions/spotify/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/spotify/stub/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1 @@
|
|||||||
|
<manifest/>
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.spotify.remoteconfig.internal;
|
||||||
|
|
||||||
|
public final class AccountAttribute {
|
||||||
|
public Object value_;
|
||||||
|
}
|
@ -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 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 final class app/revanced/patches/spotify/misc/fix/SpoofSignaturePatchKt {
|
||||||
public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
public static final fun getSpoofSignaturePatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
}
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
@ -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 }
|
|
||||||
}
|
|
@ -1,50 +1,12 @@
|
|||||||
package app.revanced.patches.spotify.navbar
|
package app.revanced.patches.spotify.navbar
|
||||||
|
|
||||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
|
||||||
import app.revanced.patcher.patch.bytecodePatch
|
import app.revanced.patcher.patch.bytecodePatch
|
||||||
import app.revanced.patcher.patch.resourcePatch
|
import app.revanced.patches.spotify.misc.unlockPremiumPatch
|
||||||
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",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Deprecated("Superseded by unlockPremiumPatch", ReplaceWith("unlockPremiumPatch"))
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
val premiumNavbarTabPatch = bytecodePatch(
|
val premiumNavbarTabPatch = bytecodePatch(
|
||||||
name = "Premium navbar tab",
|
|
||||||
description = "Hides the premium tab from the navigation bar.",
|
description = "Hides the premium tab from the navigation bar.",
|
||||||
) {
|
) {
|
||||||
dependsOn(premiumNavbarTabResourcePatch)
|
dependsOn(unlockPremiumPatch)
|
||||||
|
|
||||||
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
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user