diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch.kt index 0cd3deb8..da0d1b4a 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignaturePatch.kt @@ -18,6 +18,7 @@ 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 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.", @@ -26,14 +27,19 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction PlayerTypeHookPatch::class, PlayerResponseMethodHookPatch::class, VideoInformationPatch::class, + SpoofSignatureResourcePatch::class ] ) object SpoofSignaturePatch : BytecodePatch( setOf( - PlayerResponseModelImplFingerprint, - StoryboardThumbnailParentFingerprint, + PlayerResponseModelImplGeneralFingerprint, + PlayerResponseModelImplLiveStreamFingerprint, + PlayerResponseModelImplRecommendedLevel, StoryboardRendererSpecFingerprint, - StoryboardRendererInitFingerprint + StoryboardRendererDecoderSpecFingerprint, + StoryboardRendererDecoderRecommendedLevelFingerprint, + StoryboardThumbnailParentFingerprint, + ScrubbedPreviewLayoutFingerprint, ) ) { private const val INTEGRATIONS_CLASS_DESCRIPTOR = @@ -83,6 +89,18 @@ object SpoofSignaturePatch : BytecodePatch( "App signature not spoofed for feed videos\\n\\n" + "Feed videos will play for less than 1 minute before encountering playback issues" ) + ), + SwitchPreference( + "revanced_spoof_storyboard", + StringResource("revanced_spoof_storyboard_title", "Spoof storyboard"), + StringResource("revanced_spoof_storyboard_summary_on", "Storyboard spoofed"), + StringResource( + "revanced_spoof_storyboard_summary_off", + "Storyboard not spoofed\\n\\n" + + "Side effects include:\\n" + + "• No ambient mode\\n" + + "• Seekbar thumbnails are hidden" + ) ) ) ) @@ -94,7 +112,8 @@ object SpoofSignaturePatch : BytecodePatch( ) // Force the seekbar time and chapters to always show up. - // This is used only if the storyboard spec fetch fails, or when viewing paid videos. + // 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( @@ -124,23 +143,74 @@ object SpoofSignaturePatch : BytecodePatch( } ?: throw StoryboardThumbnailFingerprint.exception } - /** - * Hook StoryBoard renderer url - */ - PlayerResponseModelImplFingerprint.result?.let { - it.mutableMethod.apply { - val getStoryBoardIndex = it.scanResult.patternScanResult!!.endIndex - val getStoryBoardRegister = getInstruction(getStoryBoardIndex).registerA - + // If storyboard spoofing is turned off, then hide the empty seekbar thumbnail view. + ScrubbedPreviewLayoutFingerprint.result?.apply { + val endIndex = scanResult.patternScanResult!!.endIndex + mutableMethod.apply { + val imageViewFieldName = getInstruction(endIndex).reference addInstructions( - getStoryBoardIndex, + implementation!!.instructions.lastIndex, """ - invoke-static { v$getStoryBoardRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getStoryboardRendererSpec(Ljava/lang/String;)Ljava/lang/String; - move-result-object v$getStoryBoardRegister + iget-object v0, p0, $imageViewFieldName # copy imageview field to a register + invoke-static {v0}, $INTEGRATIONS_CLASS_DESCRIPTOR->seekbarImageViewCreated(Landroid/widget/ImageView;)V """ ) } - } ?: throw PlayerResponseModelImplFingerprint.exception + } ?: throw ScrubbedPreviewLayoutFingerprint.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(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(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. + PlayerResponseModelImplRecommendedLevel.result?.let { + it.mutableMethod.apply { + val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex + val originalValueRegister = + getInstruction(moveOriginalRecommendedValueIndex).registerA + + addInstructions( + moveOriginalRecommendedValueIndex, """ + invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I + move-result v$originalValueRegister + """ + ) + } + } ?: throw PlayerResponseModelImplRecommendedLevel.exception StoryboardRendererSpecFingerprint.result?.let { it.mutableMethod.apply { @@ -158,22 +228,18 @@ object SpoofSignaturePatch : BytecodePatch( } } ?: throw StoryboardRendererSpecFingerprint.exception - // Hook recommended value - StoryboardRendererInitFingerprint.result?.let { - val moveOriginalRecommendedValueIndex = it.scanResult.patternScanResult!!.endIndex + // 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(storyBoardUrlIndex).registerA - val originalValueRegister = it.mutableMethod - .getInstruction(moveOriginalRecommendedValueIndex).registerA - - it.mutableMethod.apply { - addInstructions( - moveOriginalRecommendedValueIndex + 1, - """ - invoke-static { v$originalValueRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->getRecommendedLevel(I)I - move-result v$originalValueRegister - """ - ) - } - } ?: throw StoryboardRendererInitFingerprint.exception + 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 } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignatureResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignatureResourcePatch.kt new file mode 100644 index 00000000..884d1237 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/SpoofSignatureResourcePatch.kt @@ -0,0 +1,18 @@ +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.mapping.misc.ResourceMappingPatch +import app.revanced.patches.youtube.misc.settings.SettingsPatch + +@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class]) +object SpoofSignatureResourcePatch : ResourcePatch() { + internal var scrubbedPreviewThumbnailResourceId: Long = -1 + + override fun execute(context: ResourceContext) { + scrubbedPreviewThumbnailResourceId = ResourceMappingPatch.resourceMappings.single { + it.type == "id" && it.name == "thumbnail" + }.id + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplGeneralFingerprint.kt similarity index 91% rename from src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplGeneralFingerprint.kt index 1e58bde7..fb4eadce 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplGeneralFingerprint.kt @@ -6,7 +6,7 @@ import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -object PlayerResponseModelImplFingerprint : MethodFingerprint( +object PlayerResponseModelImplGeneralFingerprint : MethodFingerprint( returnType = "Ljava/lang/String;", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = emptyList(), diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplLiveStreamFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplLiveStreamFingerprint.kt new file mode 100644 index 00000000..5dbd60c9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplLiveStreamFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.misc.fix.playback.fingerprints + +import app.revanced.extensions.containsWideLiteralInstructionValue +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +object PlayerResponseModelImplLiveStreamFingerprint : MethodFingerprint( + returnType = "Ljava/lang/String;", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = emptyList(), + opcodes = listOf( + Opcode.RETURN_OBJECT, + Opcode.CONST_4, + Opcode.RETURN_OBJECT + ), + customFingerprint = handler@{ methodDef, _ -> + if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false + + methodDef.containsWideLiteralInstructionValue(70276274) + } +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplRecommendedLevel.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplRecommendedLevel.kt new file mode 100644 index 00000000..4b7165a3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/PlayerResponseModelImplRecommendedLevel.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.misc.fix.playback.fingerprints + +import app.revanced.extensions.containsWideLiteralInstructionValue +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +object PlayerResponseModelImplRecommendedLevel : MethodFingerprint( + returnType = "I", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = emptyList(), + opcodes = listOf( + Opcode.SGET_OBJECT, + Opcode.IGET, + Opcode.RETURN + ), + customFingerprint = handler@{ methodDef, _ -> + if (!methodDef.definingClass.endsWith("/PlayerResponseModelImpl;")) return@handler false + + methodDef.containsWideLiteralInstructionValue(55735497) + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/ScrubbedPreviewLayoutFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/ScrubbedPreviewLayoutFingerprint.kt new file mode 100644 index 00000000..36bce605 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/ScrubbedPreviewLayoutFingerprint.kt @@ -0,0 +1,27 @@ +package app.revanced.patches.youtube.misc.fix.playback.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patches.youtube.misc.fix.playback.SpoofSignatureResourcePatch +import app.revanced.util.patch.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +object ScrubbedPreviewLayoutFingerprint : LiteralValueFingerprint( + accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, + returnType = "V", + parameters = listOf("Landroid/content/Context;", "Landroid/util/AttributeSet;", "I", "I"), + opcodes = listOf( + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.INVOKE_VIRTUAL, + Opcode.CONST, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.IPUT_OBJECT, // preview imageview + ), + // This resource is used in ~ 40 different locations, but this method has a distinct list of parameters to match to. + literalSupplier = { SpoofSignatureResourcePatch.scrubbedPreviewThumbnailResourceId } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt new file mode 100644 index 00000000..6ced9040 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererDecoderRecommendedLevelFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.misc.fix.playback.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +/** + * Resolves to the same method as [StoryboardRendererDecoderSpecFingerprint]. + */ +object StoryboardRendererDecoderRecommendedLevelFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"), + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IPUT_OBJECT, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT + ), + strings = listOf("#-1#") +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererDecoderSpecFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererDecoderSpecFingerprint.kt new file mode 100644 index 00000000..f0905e88 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererDecoderSpecFingerprint.kt @@ -0,0 +1,23 @@ +package app.revanced.patches.youtube.misc.fix.playback.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +/** +* Resolves to the same method as [StoryboardRendererDecoderRecommendedLevelFingerprint]. +*/ +object StoryboardRendererDecoderSpecFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;"), + opcodes = listOf( + Opcode.INVOKE_INTERFACE, // First instruction of the method. + Opcode.MOVE_RESULT_OBJECT, + Opcode.CONST_4, + Opcode.CONST_4, + Opcode.IF_NEZ, + ), + strings = listOf("#-1#") +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererInitFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererInitFingerprint.kt deleted file mode 100644 index 79ca910e..00000000 --- a/src/main/kotlin/app/revanced/patches/youtube/misc/fix/playback/fingerprints/StoryboardRendererInitFingerprint.kt +++ /dev/null @@ -1,15 +0,0 @@ -package app.revanced.patches.youtube.misc.fix.playback.fingerprints - -import app.revanced.patcher.fingerprint.MethodFingerprint -import com.android.tools.smali.dexlib2.Opcode - -object StoryboardRendererInitFingerprint : MethodFingerprint( - strings = listOf("#-1#"), - opcodes = listOf( - Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT_OBJECT, - Opcode.IPUT_OBJECT, - Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT - ) -)