feat(YouTube Music): Add Spoof player parameter patch https://github.com/inotia00/ReVanced_Extended/issues/2832

This commit is contained in:
inotia00
2025-03-14 18:18:53 +09:00
parent 806976b6d8
commit d4af0f1ee0
11 changed files with 469 additions and 17 deletions

View File

@ -14,7 +14,8 @@ import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.video.information.videoIdHook
import app.revanced.patches.music.video.information.videoInformationPatch
import app.revanced.patches.music.video.playerresponse.hookPlayerResponse
import app.revanced.patches.music.video.playerresponse.Hook
import app.revanced.patches.music.video.playerresponse.addPlayerResponseMethodHook
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.methodOrThrow
@ -46,7 +47,11 @@ val albumMusicVideoPatch = bytecodePatch(
// region hook player response
hookPlayerResponse("$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V")
addPlayerResponseMethodHook(
Hook.VideoIdAndPlaylistId(
"$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponse(Ljava/lang/String;Ljava/lang/String;I)V"
),
)
// endregion

View File

@ -0,0 +1,26 @@
package app.revanced.patches.music.utils.fix.parameter
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
internal val subtitleWindowFingerprint = legacyFingerprint(
name = "subtitleWindowFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
parameters = listOf("I", "I", "I", "Z", "Z"),
strings = listOf("invalid anchorHorizontalPos: %s"),
)
/**
* If this flag is activated, a playback issue occurs in age-restricted videos.
*/
internal const val AGE_RESTRICTED_PLAYBACK_FEATURE_FLAG = 45651506L
internal val ageRestrictedPlaybackFeatureFlagFingerprint = legacyFingerprint(
name = "ageRestrictedPlaybackFeatureFlagFingerprint",
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
literals = listOf(AGE_RESTRICTED_PLAYBACK_FEATURE_FLAG),
)

View File

@ -0,0 +1,82 @@
package app.revanced.patches.music.utils.fix.parameter
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_PLAYER_PARAMETER
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.video.information.videoInformationPatch
import app.revanced.patches.music.video.playerresponse.Hook
import app.revanced.patches.music.video.playerresponse.addPlayerResponseMethodHook
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.resolvable
private const val EXTENSION_CLASS_DESCRIPTOR =
"$MISC_PATH/SpoofPlayerParameterPatch;"
@Suppress("unused")
val spoofPlayerParameterPatch = bytecodePatch(
SPOOF_PLAYER_PARAMETER.title,
SPOOF_PLAYER_PARAMETER.summary
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
videoInformationPatch,
playerResponseMethodHookPatch,
)
execute {
addPlayerResponseMethodHook(
Hook.PlayerParameter(
"$EXTENSION_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
),
)
// region fix for subtitles position
subtitleWindowFingerprint.methodOrThrow().addInstructions(
0,
"""
invoke-static {p1, p2, p3, p4, p5}, $EXTENSION_CLASS_DESCRIPTOR->fixSubtitleWindowPosition(IIIZZ)[I
move-result-object v0
const/4 v1, 0x0
aget p1, v0, v1 # ap, anchor position
const/4 v1, 0x1
aget p2, v0, v1 # ah, horizontal anchor
const/4 v1, 0x2
aget p3, v0, v1 # av, vertical anchor
"""
)
// endregion
// region fix for feature flags
if (ageRestrictedPlaybackFeatureFlagFingerprint.resolvable()) {
ageRestrictedPlaybackFeatureFlagFingerprint.injectLiteralInstructionBooleanCall(
AGE_RESTRICTED_PLAYBACK_FEATURE_FLAG,
"$EXTENSION_CLASS_DESCRIPTOR->forceDisableAgeRestrictedPlaybackFeatureFlag(Z)Z"
)
}
// endregion
addSwitchPreference(
CategoryType.MISC,
"revanced_spoof_player_parameter",
"true"
)
updatePatchStatus(SPOOF_PLAYER_PARAMETER)
}
}

View File

@ -157,6 +157,10 @@ internal enum class PatchList(
"Spoof client",
"Adds options to spoof the client to allow playback."
),
SPOOF_PLAYER_PARAMETER(
"Spoof player parameter",
"Adds options to spoof player parameter to allow playback."
),
TRANSLATIONS_FOR_YOUTUBE_MUSIC(
"Translations for YouTube Music",
"Add translations or remove string resources."

View File

@ -13,6 +13,9 @@ import app.revanced.patches.music.utils.extension.Constants.SHARED_PATH
import app.revanced.patches.music.utils.playbackSpeedFingerprint
import app.revanced.patches.music.utils.playbackSpeedParentFingerprint
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.video.playerresponse.Hook
import app.revanced.patches.music.video.playerresponse.addPlayerResponseMethodHook
import app.revanced.patches.music.video.playerresponse.playerResponseMethodHookPatch
import app.revanced.patches.shared.mdxPlayerDirectorSetVideoStageFingerprint
import app.revanced.patches.shared.videoLengthFingerprint
import app.revanced.util.addStaticFieldToExtension
@ -71,7 +74,10 @@ private var videoTimeConstructorInsertIndex = 2
val videoInformationPatch = bytecodePatch(
description = "videoInformationPatch",
) {
dependsOn(sharedResourceIdPatch)
dependsOn(
playerResponseMethodHookPatch,
sharedResourceIdPatch
)
execute {
fun addSeekInterfaceMethods(
@ -241,7 +247,18 @@ val videoInformationPatch = bytecodePatch(
* Set current video id
*/
videoIdHook("$EXTENSION_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V")
addPlayerResponseMethodHook(
Hook.VideoId(
"$EXTENSION_CLASS_DESCRIPTOR->setPlayerResponseVideoId(Ljava/lang/String;)V"
),
)
// Call before any other video id hooks,
// so they can use VideoInformation and check if the video id is for a Short.
addPlayerResponseMethodHook(
Hook.PlayerParameterBeforeVideoId(
"$EXTENSION_CLASS_DESCRIPTOR->newPlayerResponseParameter(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"
)
)
/**
* Hook current playback speed
*/

View File

@ -11,8 +11,7 @@ private val PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST = listOf(
"[B",
"Ljava/lang/String;", // Player parameters proto buffer.
"Ljava/lang/String;", // PlaylistId.
"I", // PlaylistIndex.
"I"
"I" // PlaylistIndex.
)
/**
@ -30,7 +29,7 @@ internal val playerParameterBuilderFingerprint = legacyFingerprint(
return@custom false
}
val startsWithMethodParameterList = parameterTypes.slice(0..5)
val startsWithMethodParameterList = parameterTypes.slice(0..4)
parametersEqual(
PLAYER_PARAMETER_STARTS_WITH_PARAMETER_LIST,

View File

@ -1,22 +1,35 @@
package app.revanced.patches.music.video.playerresponse
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.music.utils.extension.sharedExtensionPatch
import app.revanced.patches.music.utils.playservice.is_7_03_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.util.fingerprint.methodOrThrow
private val hooks = mutableSetOf<Hook>()
fun addPlayerResponseMethodHook(hook: Hook) {
hooks += hook
}
private const val REGISTER_VIDEO_ID = "p1"
private const val REGISTER_PLAYER_PARAMETER = "p3"
private const val REGISTER_PLAYLIST_ID = "p4"
private const val REGISTER_PLAYLIST_INDEX = "p5"
private lateinit var playerResponseMethod: MutableMethod
private var numberOfInstructionsAdded = 0
val playerResponseMethodHookPatch = bytecodePatch(
description = "playerResponseMethodHookPatch"
) {
dependsOn(versionCheckPatch)
dependsOn(
sharedExtensionPatch,
versionCheckPatch,
)
execute {
playerResponseMethod = if (is_7_03_or_greater) {
@ -25,16 +38,70 @@ val playerResponseMethodHookPatch = bytecodePatch(
playerParameterBuilderLegacyFingerprint
}.methodOrThrow()
}
finalize {
fun hookVideoId(hook: Hook) {
playerResponseMethod.addInstruction(
0,
"invoke-static {$REGISTER_VIDEO_ID}, $hook",
)
numberOfInstructionsAdded++
}
fun hookVideoIdAndPlaylistId(hook: Hook) {
playerResponseMethod.addInstruction(
0,
"invoke-static {$REGISTER_VIDEO_ID, $REGISTER_PLAYLIST_ID, $REGISTER_PLAYLIST_INDEX}, $hook",
)
numberOfInstructionsAdded++
}
fun hookPlayerParameter(hook: Hook) {
playerResponseMethod.addInstructions(
0,
"""
invoke-static {$REGISTER_VIDEO_ID, v0}, $hook
move-result-object v0
""",
)
numberOfInstructionsAdded += 2
}
// Reverse the order in order to preserve insertion order of the hooks.
val beforeVideoIdHooks =
hooks.filterIsInstance<Hook.PlayerParameterBeforeVideoId>().asReversed()
val videoIdHooks = hooks.filterIsInstance<Hook.VideoId>().asReversed()
val videoIdAndPlaylistIdHooks = hooks.filterIsInstance<Hook.VideoIdAndPlaylistId>().asReversed()
val afterVideoIdHooks = hooks.filterIsInstance<Hook.PlayerParameter>().asReversed()
// Add the hooks in this specific order as they insert instructions at the beginning of the method.
afterVideoIdHooks.forEach(::hookPlayerParameter)
videoIdAndPlaylistIdHooks.forEach(::hookVideoIdAndPlaylistId)
videoIdHooks.forEach(::hookVideoId)
beforeVideoIdHooks.forEach(::hookPlayerParameter)
playerResponseMethod.apply {
addInstruction(
0,
"move-object/from16 v0, $REGISTER_PLAYER_PARAMETER"
)
numberOfInstructionsAdded++
// Move the modified register back.
addInstruction(
numberOfInstructionsAdded,
"move-object/from16 $REGISTER_PLAYER_PARAMETER, v0"
)
}
}
}
fun hookPlayerResponse(
descriptor: String,
onlyVideoId: Boolean = false
) {
val smaliInstruction = if (onlyVideoId)
"invoke-static {$REGISTER_VIDEO_ID}, $descriptor"
else
"invoke-static {$REGISTER_VIDEO_ID, $REGISTER_PLAYLIST_ID, $REGISTER_PLAYLIST_INDEX}, $descriptor"
sealed class Hook(private val methodDescriptor: String) {
class VideoId(methodDescriptor: String) : Hook(methodDescriptor)
class VideoIdAndPlaylistId(methodDescriptor: String) : Hook(methodDescriptor)
playerResponseMethod.addInstruction(0, smaliInstruction)
class PlayerParameter(methodDescriptor: String) : Hook(methodDescriptor)
class PlayerParameterBeforeVideoId(methodDescriptor: String) : Hook(methodDescriptor)
override fun toString() = methodDescriptor
}