diff --git a/extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/UnlockPremiumPatch.java b/extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/UnlockPremiumPatch.java index e58afe575e..38283542f2 100644 --- a/extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/UnlockPremiumPatch.java +++ b/extensions/spotify/src/main/java/app/revanced/extension/spotify/misc/UnlockPremiumPatch.java @@ -3,7 +3,6 @@ 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; @@ -15,6 +14,25 @@ @SuppressWarnings("unused") public final class UnlockPremiumPatch { + private static final String SPOTIFY_MAIN_ACTIVITY_LEGACY = "com.spotify.music.MainActivity"; + + /** + * If the app target is 8.6.98.900. + */ + private static final boolean IS_SPOTIFY_LEGACY_APP_TARGET; + + static { + boolean legacy; + try { + Class.forName(SPOTIFY_MAIN_ACTIVITY_LEGACY); + legacy = true; + } catch (ClassNotFoundException ex) { + legacy = false; + } + + IS_SPOTIFY_LEGACY_APP_TARGET = legacy; + } + private static class OverrideAttribute { /** * Account attribute key. @@ -55,8 +73,8 @@ private static class OverrideAttribute { // Make sure playing songs is not disabled remotely and playlists show up. new OverrideAttribute("streaming", TRUE), // Allows adding songs to queue and removes the smart shuffle mode restriction, - // allowing to pick any of the other modes. - new OverrideAttribute("pick-and-shuffle", FALSE), + // allowing to pick any of the other modes. Flag is not present in legacy app target. + new OverrideAttribute("pick-and-shuffle", FALSE, !IS_SPOTIFY_LEGACY_APP_TARGET), // Disables shuffle-mode streaming-rule, which forces songs to be played shuffled // and breaks the player when other patches are applied. new OverrideAttribute("streaming-rules", ""), @@ -78,7 +96,7 @@ private static class OverrideAttribute { /** * Override attributes injection point. */ - public static void overrideAttribute(Map attributes) { + public static void overrideAttribute(Map attributes) { try { for (var override : OVERRIDES) { var attribute = attributes.get(override.key); @@ -87,7 +105,12 @@ public static void overrideAttribute(Map attributes) { Logger.printException(() -> "'" + override.key + "' expected but not found"); } } else { - attribute.value_ = override.overrideValue; + Object overrideValue = override.overrideValue; + if (IS_SPOTIFY_LEGACY_APP_TARGET) { + ((com.spotify.useraccount.v1.AccountAttribute) attribute).value_ = overrideValue; + } else { + ((com.spotify.remoteconfig.internal.AccountAttribute) attribute).value_ = overrideValue; + } } } } catch (Exception ex) { diff --git a/extensions/spotify/stub/src/main/java/com/spotify/useraccount/v1/AccountAttribute.java b/extensions/spotify/stub/src/main/java/com/spotify/useraccount/v1/AccountAttribute.java new file mode 100644 index 0000000000..a03b583bf5 --- /dev/null +++ b/extensions/spotify/stub/src/main/java/com/spotify/useraccount/v1/AccountAttribute.java @@ -0,0 +1,8 @@ +package com.spotify.useraccount.v1; + +/** + * Used for target 8.6.98.900. Class is still present in newer app targets. + */ +public class AccountAttribute { + public Object value_; +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt index 5233f186f1..40ced06a0a 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/extension/SharedExtensionPatch.kt @@ -92,7 +92,7 @@ fun sharedExtensionPatch( } class ExtensionHook internal constructor( - private val fingerprint: Fingerprint, + internal val fingerprint: Fingerprint, private val insertIndexResolver: ((Method) -> Int), private val contextRegisterResolver: (Method) -> String, ) { diff --git a/patches/src/main/kotlin/app/revanced/patches/spotify/layout/theme/CustomThemeBytecodePatch.kt b/patches/src/main/kotlin/app/revanced/patches/spotify/layout/theme/CustomThemeBytecodePatch.kt index cdedbfd6aa..2f639ef8da 100644 --- a/patches/src/main/kotlin/app/revanced/patches/spotify/layout/theme/CustomThemeBytecodePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/spotify/layout/theme/CustomThemeBytecodePatch.kt @@ -5,6 +5,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.fingerprint import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch import app.revanced.util.* import com.android.tools.smali.dexlib2.AccessFlags @@ -21,6 +22,12 @@ internal val customThemeByteCodePatch = bytecodePatch { val backgroundColorSecondary by spotifyBackgroundColorSecondary execute { + if (IS_SPOTIFY_LEGACY_APP_TARGET) { + // Bytecode changes are not needed for legacy app target. + // Player background color is changed with existing resource patch. + return@execute + } + fun MutableMethod.addColorChangeInstructions(literal: Long, colorString: String) { val index = indexOfFirstLiteralInstructionOrThrow(literal) val register = getInstruction(index).registerA 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 b23f405f4f..29f472a5d1 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,29 @@ package app.revanced.patches.spotify.misc import app.revanced.patcher.fingerprint +import app.revanced.patches.spotify.misc.extension.IS_SPOTIFY_LEGACY_APP_TARGET import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode internal val accountAttributeFingerprint = fingerprint { - custom { _, classDef -> classDef.endsWith("internal/AccountAttribute;") } + custom { _, classDef -> + classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) { + "Lcom/spotify/useraccount/v1/AccountAttribute;" + } else { + "Lcom/spotify/remoteconfig/internal/AccountAttribute;" + } + } } internal val productStateProtoFingerprint = fingerprint { returns("Ljava/util/Map;") - custom { _, classDef -> classDef.endsWith("ProductStateProto;") } + custom { _, classDef -> + classDef.type == if (IS_SPOTIFY_LEGACY_APP_TARGET) { + "Lcom/spotify/ucs/proto/v0/UcsResponseWrapper${'$'}AccountAttributesResponse;" + } else { + "Lcom/spotify/remoteconfig/internal/ProductStateProto;" + } + } } internal val buildQueryParametersFingerprint = fingerprint { diff --git a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/UnlockPremiumPatch.kt b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/UnlockPremiumPatch.kt index eee224f17d..183bbb9080 100644 --- a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/UnlockPremiumPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/UnlockPremiumPatch.kt @@ -6,6 +6,7 @@ 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.IS_SPOTIFY_LEGACY_APP_TARGET import app.revanced.patches.spotify.misc.extension.sharedExtensionPatch import app.revanced.util.* import com.android.tools.smali.dexlib2.AccessFlags @@ -14,6 +15,7 @@ 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 +import java.util.logging.Logger private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/spotify/misc/UnlockPremiumPatch;" @@ -53,6 +55,12 @@ val unlockPremiumPatch = bytecodePatch( method.replaceInstruction(addQueryParameterConditionIndex, "nop") } + if (IS_SPOTIFY_LEGACY_APP_TARGET) { + return@execute Logger.getLogger(this::class.java.name).info( + "Patching a legacy Spotify version. Patch functionality may be limited." + ) + } + // Disable the "Spotify Premium" upsell experiment in context menus. with(contextMenuExperimentsFingerprint) { val moveIsEnabledIndex = method.indexOfFirstInstructionOrThrow( diff --git a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/extension/ExtensionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/extension/ExtensionPatch.kt index 6b95f437a3..98afe65b22 100644 --- a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/extension/ExtensionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/extension/ExtensionPatch.kt @@ -1,5 +1,20 @@ package app.revanced.patches.spotify.misc.extension +import app.revanced.patcher.patch.bytecodePatch import app.revanced.patches.shared.misc.extension.sharedExtensionPatch -val sharedExtensionPatch = sharedExtensionPatch("spotify", spotifyMainActivityOnCreate) +/** + * If patching a legacy 8.x target. This may also be set if patching slightly older/newer app targets, + * but the only legacy target of interest is 8.6.98.900 as it's the last version that + * supports Spotify integration on Kenwood/Pioneer car stereos. + */ +internal var IS_SPOTIFY_LEGACY_APP_TARGET = false + +val sharedExtensionPatch = bytecodePatch { + dependsOn(sharedExtensionPatch("spotify", spotifyMainActivityOnCreate)) + + execute { + IS_SPOTIFY_LEGACY_APP_TARGET = spotifyMainActivityOnCreate.fingerprint + .originalClassDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/extension/Hooks.kt b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/extension/Hooks.kt index baed926eac..956c5610e4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/spotify/misc/extension/Hooks.kt +++ b/patches/src/main/kotlin/app/revanced/patches/spotify/misc/extension/Hooks.kt @@ -2,9 +2,16 @@ package app.revanced.patches.spotify.misc.extension import app.revanced.patches.shared.misc.extension.extensionHook +private const val SPOTIFY_MAIN_ACTIVITY = "Lcom/spotify/music/SpotifyMainActivity;" + +/** + * Main activity of target 8.6.98.900. + */ +internal const val SPOTIFY_MAIN_ACTIVITY_LEGACY = "Lcom/spotify/music/MainActivity;" + internal val spotifyMainActivityOnCreate = extensionHook { custom { method, classDef -> - classDef.type == "Lcom/spotify/music/SpotifyMainActivity;" && - method.name == "onCreate" + method.name == "onCreate" && (classDef.type == SPOTIFY_MAIN_ACTIVITY + || classDef.type == SPOTIFY_MAIN_ACTIVITY_LEGACY) } }