Skip to content

Commit

Permalink
fix(YouTube): Fix issues related to playback by replace streaming data (
Browse files Browse the repository at this point in the history
#3582)

Co-authored-by: LisoUseInAIKyrios <[email protected]>
Co-authored-by: oSumAtrIX <[email protected]>
  • Loading branch information
3 people authored Sep 17, 2024
1 parent 17126df commit dfa94d7
Show file tree
Hide file tree
Showing 30 changed files with 427 additions and 1,126 deletions.
6 changes: 6 additions & 0 deletions api/revanced-patches.api
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,12 @@ public final class app/revanced/patches/youtube/misc/fix/playback/SpoofSignature
public fun execute (Lapp/revanced/patcher/data/ResourceContext;)V
}

public final class app/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch : app/revanced/patcher/patch/BytecodePatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/SpoofVideoStreamsPatch;
public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V
public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V
}

public final class app/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch : app/revanced/patches/all/misc/transformation/BaseTransformInstructionsPatch {
public static final field INSTANCE Lapp/revanced/patches/youtube/misc/fix/playback/UserAgentClientSpoofPatch;
public synthetic fun filterMap (Lcom/android/tools/smali/dexlib2/iface/ClassDef;Lcom/android/tools/smali/dexlib2/iface/Method;Lcom/android/tools/smali/dexlib2/iface/instruction/Instruction;I)Ljava/lang/Object;
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,239 +1,12 @@
package app.revanced.patches.youtube.misc.fix.playback

import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreen.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.fix.playback.fingerprints.*
import app.revanced.patches.youtube.misc.playertype.PlayerTypeHookPatch
import app.revanced.patches.youtube.misc.settings.SettingsPatch
import app.revanced.patches.youtube.video.information.VideoInformationPatch
import app.revanced.patches.youtube.video.playerresponse.PlayerResponseMethodHookPatch
import app.revanced.util.exception
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.ReferenceInstruction

@Patch(
description = "Spoofs the signature to prevent playback issues.",
dependencies = [
SettingsPatch::class,
PlayerTypeHookPatch::class,
PlayerResponseMethodHookPatch::class,
VideoInformationPatch::class,
SpoofSignatureResourcePatch::class,
AddResourcesPatch::class,
],
)
@Deprecated("This patch will be removed in the future.")
@Deprecated("This patch is obsolete.", replaceWith = ReplaceWith("SpoofVideoStreamsPatch"))
object SpoofSignaturePatch : BytecodePatch(
setOf(
PlayerResponseModelImplGeneralFingerprint,
PlayerResponseModelImplLiveStreamFingerprint,
PlayerResponseModelImplRecommendedLevelFingerprint,
StoryboardRendererSpecFingerprint,
StoryboardRendererDecoderSpecFingerprint,
StoryboardRendererDecoderRecommendedLevelFingerprint,
StoryboardThumbnailParentFingerprint,
SpoofSignaturePatchScrubbedPreviewLayoutFingerprint,
StatsQueryParameterFingerprint,
ParamsMapPutFingerprint,
),
dependencies = setOf(SpoofVideoStreamsPatch::class),
) {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/youtube/patches/spoof/SpoofSignaturePatch;"

override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class)

SettingsPatch.PreferenceScreen.MISC.addPreferences(
PreferenceScreen(
key = "revanced_spoof_signature_verification_screen",
sorting = Sorting.UNSORTED,
preferences = setOf(
SwitchPreference("revanced_spoof_signature_verification_enabled"),
SwitchPreference("revanced_spoof_signature_in_feed_enabled"),
SwitchPreference("revanced_spoof_storyboard"),
),
),
)

// Hook the player parameters.
PlayerResponseMethodHookPatch += PlayerResponseMethodHookPatch.Hook.ProtoBufferParameter(
"$INTEGRATIONS_CLASS_DESCRIPTOR->spoofParameter(Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/String;",
)

// Force the seekbar time and chapters to always show up.
// This is used if the storyboard spec fetch fails, for viewing paid videos,
// or if storyboard spoofing is turned off.
StoryboardThumbnailParentFingerprint.result?.classDef?.let { classDef ->
StoryboardThumbnailFingerprint.also {
it.resolve(
context,
classDef,
)
}.result?.let {
val endIndex = it.scanResult.patternScanResult!!.endIndex
// Replace existing instruction to preserve control flow label.
// The replaced return instruction always returns false
// (it is the 'no thumbnails found' control path),
// so there is no need to pass the existing return value to integrations.
it.mutableMethod.replaceInstruction(
endIndex,
"""
invoke-static {}, $INTEGRATIONS_CLASS_DESCRIPTOR->getSeekbarThumbnailOverrideValue()Z
""",
)
// Since this is end of the method must replace one line then add the rest.
it.mutableMethod.addInstructions(
endIndex + 1,
"""
move-result v0
return v0
""",
)
} ?: throw StoryboardThumbnailFingerprint.exception
}

// If storyboard spoofing is turned off, then hide the empty seekbar thumbnail view.
SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.result?.apply {
val endIndex = scanResult.patternScanResult!!.endIndex
mutableMethod.apply {
val imageViewFieldName = getInstruction<ReferenceInstruction>(endIndex).reference
addInstructions(
implementation!!.instructions.lastIndex,
"""
iget-object v0, p0, $imageViewFieldName # copy imageview field to a register
invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->seekbarImageViewCreated(Landroid/widget/ImageView;)V
""",
)
}
} ?: throw SpoofSignaturePatchScrubbedPreviewLayoutFingerprint.exception

/**
* Hook StoryBoard renderer url
*/
arrayOf(
PlayerResponseModelImplGeneralFingerprint,
PlayerResponseModelImplLiveStreamFingerprint,
).forEach { fingerprint ->
fingerprint.result?.let {
it.mutableMethod.apply {
val getStoryBoardIndex = it.scanResult.patternScanResult!!.endIndex
val getStoryBoardRegister =
getInstruction<OneRegisterInstruction>(getStoryBoardIndex).registerA

addInstructions(
getStoryBoardIndex,
"""
invoke-static { v$getStoryBoardRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$getStoryBoardRegister
""",
)
}
} ?: throw fingerprint.exception
}

// Hook recommended seekbar thumbnails quality level.
StoryboardRendererDecoderRecommendedLevelFingerprint.result?.let {
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
val originalValueRegister = it.mutableMethod
.getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA

it.mutableMethod.addInstructions(
moveOriginalRecommendedValueIndex + 1,
"""
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
move-result v$originalValueRegister
""",
)
} ?: throw StoryboardRendererDecoderRecommendedLevelFingerprint.exception

// Hook the recommended precise seeking thumbnails quality level.
PlayerResponseModelImplRecommendedLevelFingerprint.result?.let {
it.mutableMethod.apply {
val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex
val originalValueRegister =
getInstruction<OneRegisterInstruction>(moveOriginalRecommendedValueIndex).registerA

addInstructions(
moveOriginalRecommendedValueIndex,
"""
invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I
move-result v$originalValueRegister
""",
)
}
} ?: throw PlayerResponseModelImplRecommendedLevelFingerprint.exception

StoryboardRendererSpecFingerprint.result?.let {
it.mutableMethod.apply {
val storyBoardUrlParams = 0

addInstructionsWithLabels(
0,
"""
if-nez p$storyBoardUrlParams, :ignore
invoke-static { p$storyBoardUrlParams }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object p$storyBoardUrlParams
""",
ExternalLabel("ignore", getInstruction(0)),
)
}
} ?: throw StoryboardRendererSpecFingerprint.exception

// Hook the seekbar thumbnail decoder and use a NULL spec for live streams.
StoryboardRendererDecoderSpecFingerprint.result?.let {
val storyBoardUrlIndex = it.scanResult.patternScanResult!!.startIndex + 1
val storyboardUrlRegister =
it.mutableMethod.getInstruction<OneRegisterInstruction>(storyBoardUrlIndex).registerA

it.mutableMethod.addInstructions(
storyBoardUrlIndex + 1,
"""
invoke-static { v$storyboardUrlRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardDecoderRendererSpec(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$storyboardUrlRegister
""",
)
} ?: throw StoryboardRendererDecoderSpecFingerprint.exception

// Fix stats not being tracked.
// Due to signature spoofing "adformat" is present in query parameters made for /stats requests,
// even though, for regular videos, it should not be.
// This breaks stats tracking.
// Replace the ad parameter with the video parameter in the query parameters.
StatsQueryParameterFingerprint.result?.let {
val putMethod = ParamsMapPutFingerprint.result?.method?.toString()
?: throw ParamsMapPutFingerprint.exception

it.mutableMethod.apply {
val adParamIndex = it.scanResult.stringsScanResult!!.matches.first().index
val videoParamIndex = adParamIndex + 3

// Replace the ad parameter with the video parameter.
replaceInstruction(adParamIndex, getInstruction(videoParamIndex))

// Call paramsMap.put instead of paramsMap.putIfNotExist
// because the key is already present in the map.
val putAdParamIndex = adParamIndex + 1
val putIfKeyNotExistsInstruction = getInstruction<FiveRegisterInstruction>(putAdParamIndex)
replaceInstruction(
putAdParamIndex,
"invoke-virtual { " +
"v${putIfKeyNotExistsInstruction.registerC}, " +
"v${putIfKeyNotExistsInstruction.registerD}, " +
"v${putIfKeyNotExistsInstruction.registerE} }, " +
putMethod,
)
}
} ?: throw StatsQueryParameterFingerprint.exception
}
override fun execute(context: BytecodeContext) {}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,8 @@ package app.revanced.patches.youtube.misc.fix.playback

import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch

@Patch(dependencies = [ResourceMappingPatch::class])
@Deprecated("This patch will be removed in the future.")
object SpoofSignatureResourcePatch : ResourcePatch() {
internal var scrubbedPreviewThumbnailResourceId: Long = -1

override fun execute(context: ResourceContext) {
scrubbedPreviewThumbnailResourceId = ResourceMappingPatch[
"id",
"thumbnail",
]
}
override fun execute(context: ResourceContext) {}
}
Loading

0 comments on commit dfa94d7

Please sign in to comment.