fix(Spotify - Unlock Spotify Premium): Remove restrictions for Google voice assistant (#4702)

Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
Brosssh 2025-04-11 21:33:59 +02:00 committed by GitHub
parent e5ffd2c353
commit 106202f9eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 76 additions and 4 deletions

View File

@ -94,7 +94,7 @@ public final class UnlockPremiumPatch {
);
/**
* Override attributes injection point.
* Injection point. Override account attributes.
*/
public static void overrideAttribute(Map<String, /*AccountAttribute*/ Object> attributes) {
try {
@ -119,7 +119,14 @@ public final class UnlockPremiumPatch {
}
/**
* Remove ads sections from home injection point.
* Injection point. Remove station data from Google assistant URI.
*/
public static String removeStationString(String spotifyUriOrUrl) {
return spotifyUriOrUrl.replace("spotify:station:", "spotify:");
}
/**
* Injection point. Remove ads sections from home.
*/
public static void removeHomeSections(List<Section> sections) {
try {

View File

@ -35,6 +35,27 @@ internal val contextMenuExperimentsFingerprint = fingerprint {
strings("remove_ads_upsell_enabled")
}
internal val contextFromJsonFingerprint = fingerprint {
opcodes(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC
)
custom { methodDef, classDef ->
methodDef.name == "fromJson" &&
classDef.endsWith("voiceassistants/playermodels/ContextJsonAdapter;")
}
}
internal val readPlayerOptionOverridesFingerprint = fingerprint {
custom { methodDef, classDef ->
methodDef.name == "readPlayerOptionOverrides" &&
classDef.endsWith("voiceassistants/playermodels/PreparePlayOptionsJsonAdapter;")
}
}
internal val homeSectionFingerprint = fingerprint {
custom { _, classDef -> classDef.endsWith("homeapi/proto/Section;") }
}

View File

@ -1,6 +1,7 @@
package app.revanced.patches.spotify.misc
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
@ -8,9 +9,12 @@ 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 app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
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
@ -47,6 +51,7 @@ val unlockPremiumPatch = bytecodePatch(
)
}
// Add the query parameter trackRows to show popular tracks in the artist page.
with(buildQueryParametersFingerprint) {
val addQueryParameterConditionIndex = method.indexOfFirstInstructionReversedOrThrow(
@ -55,12 +60,50 @@ val unlockPremiumPatch = bytecodePatch(
method.replaceInstruction(addQueryParameterConditionIndex, "nop")
}
if (IS_SPOTIFY_LEGACY_APP_TARGET) {
return@execute Logger.getLogger(this::class.java.name).info(
return@execute Logger.getLogger(this::class.java.name).warning(
"Patching a legacy Spotify version. Patch functionality may be limited."
)
}
// Enable choosing a specific song/artist via Google Assistant.
contextFromJsonFingerprint.method.apply {
val insertIndex = contextFromJsonFingerprint.patternMatch!!.startIndex
// Both the URI and URL need to be modified.
val registerUrl = getInstruction<FiveRegisterInstruction>(insertIndex).registerC
val registerUri = getInstruction<FiveRegisterInstruction>(insertIndex + 2).registerD
val extensionMethodDescriptor = "$EXTENSION_CLASS_DESCRIPTOR->" +
"removeStationString(Ljava/lang/String;)Ljava/lang/String;"
addInstructions(
insertIndex,
"""
invoke-static { v$registerUrl }, $extensionMethodDescriptor
move-result-object v$registerUrl
invoke-static { v$registerUri }, $extensionMethodDescriptor
move-result-object v$registerUri
"""
)
}
// Disable forced shuffle when asking for an album/playlist via Google Assistant.
readPlayerOptionOverridesFingerprint.method.apply {
val shufflingContextCallIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "shufflingContext"
}
val registerBool = getInstruction<FiveRegisterInstruction>(shufflingContextCallIndex).registerD
addInstruction(
shufflingContextCallIndex,
"sget-object v$registerBool, Ljava/lang/Boolean;->FALSE:Ljava/lang/Boolean;"
)
}
// Disable the "Spotify Premium" upsell experiment in context menus.
with(contextMenuExperimentsFingerprint) {
val moveIsEnabledIndex = method.indexOfFirstInstructionOrThrow(
@ -70,6 +113,7 @@ val unlockPremiumPatch = bytecodePatch(
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())