Skip to content

Commit

Permalink
feat(YouTube): Add Alternative thumbnails patch (#2834)
Browse files Browse the repository at this point in the history
Co-authored-by: oSumAtrIX <[email protected]>
  • Loading branch information
LisoUseInAIKyrios and oSumAtrIX authored Aug 21, 2023
1 parent c92ed01 commit 8a4277c
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package app.revanced.patches.youtube.layout.alternativethumbnails.annotations

import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package

@Compatibility([Package("com.google.android.youtube", arrayOf("18.20.39", "18.23.35"))])
@Target(AnnotationTarget.CLASS)
internal annotation class AlternativeThumbnailsCompatibility
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags

object CronetURLRequestCallbackOnFailureFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;", "Lorg/chromium/net/CronetException;"),
customFingerprint = { methodDef, _ ->
methodDef.name == "onFailed"
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags

// Acts as a parent fingerprint.
object CronetURLRequestCallbackOnResponseStartedFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;"),
strings = listOf(
"Content-Length",
"Content-Type",
"identity",
"application/x-protobuf"
),
customFingerprint = { methodDef, _ ->
methodDef.name == "onResponseStarted"
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags

object CronetURLRequestCallbackOnSucceededFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;"),
customFingerprint = { methodDef, _ ->
methodDef.name == "onSucceeded"
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags

object MessageDigestImageUrlFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
parameters = listOf("Ljava/lang/String;", "L")
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints

import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags

object MessageDigestImageUrlParentFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
returnType = "Ljava/lang/String;",
parameters = listOf(),
strings = listOf("@#&=*+-_.,:!?()/~'%;\$"),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.patch

import app.revanced.extensions.toErrorResult
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.shared.settings.preference.impl.ArrayResource
import app.revanced.patches.shared.settings.preference.impl.ListPreference
import app.revanced.patches.shared.settings.preference.impl.NonInteractivePreference
import app.revanced.patches.shared.settings.preference.impl.PreferenceScreen
import app.revanced.patches.shared.settings.preference.impl.StringResource
import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
import app.revanced.patches.youtube.layout.alternativethumbnails.annotations.AlternativeThumbnailsCompatibility
import app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints.CronetURLRequestCallbackOnFailureFingerprint
import app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints.CronetURLRequestCallbackOnResponseStartedFingerprint
import app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints.CronetURLRequestCallbackOnSucceededFingerprint
import app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints.MessageDigestImageUrlFingerprint
import app.revanced.patches.youtube.layout.alternativethumbnails.bytecode.fingerprints.MessageDigestImageUrlParentFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch

@Patch
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Name("Alternative thumbnails")
@AlternativeThumbnailsCompatibility
@Description("Adds an option to replace video thumbnails with still image captures of the video.")
class AlternativeThumbnailsPatch : BytecodePatch(
listOf(MessageDigestImageUrlParentFingerprint, CronetURLRequestCallbackOnResponseStartedFingerprint)
) {
override fun execute(context: BytecodeContext): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
PreferenceScreen(
"revanced_alt_thumbnails_preference_screen",
StringResource("revanced_alt_thumbnails_preference_screen_title", "Alternative thumbnails"),
listOf(
SwitchPreference(
"revanced_alt_thumbnail",
StringResource("revanced_alt_thumbnail_title", "Enable alternative thumbnails"),
StringResource("revanced_alt_thumbnail_summary_on", "YouTube video stills shown"),
StringResource("revanced_alt_thumbnail_summary_off", "Original YouTube thumbnails shown")
),
ListPreference(
"revanced_alt_thumbnail_type",
StringResource("revanced_alt_thumbnail_type_title", "Video time to take the still from"),
ArrayResource(
"revanced_alt_thumbnail_type_entries",
listOf(
StringResource("revanced_alt_thumbnail_type_entry_1", "Beginning of video"),
StringResource("revanced_alt_thumbnail_type_entry_2", "Middle of video"),
StringResource("revanced_alt_thumbnail_type_entry_3", "End of video"),
)
),
ArrayResource(
"revanced_alt_thumbnail_type_entry_values",
listOf(
StringResource("revanced_alt_thumbnail_type_entry_value_1", "1"),
StringResource("revanced_alt_thumbnail_type_entry_value_2", "2"),
StringResource("revanced_alt_thumbnail_type_entry_value_3", "3"),
)
)
),
SwitchPreference(
"revanced_alt_thumbnail_fast_quality",
StringResource("revanced_alt_thumbnail_fast_quality_title", "Use fast alternative thumbnails"),
StringResource("revanced_alt_thumbnail_fast_quality_summary_on", "Using medium quality stills. Thumbnails will load faster, but live streams, unreleased, or very old videos may show blank thumbnails"),
StringResource("revanced_alt_thumbnail_fast_quality_summary_off", "Using high quality stills")
),
NonInteractivePreference(
StringResource("revanced_alt_thumbnail_about_title", "About"),
StringResource("revanced_alt_thumbnail_about_summary", "Alternative thumbnails are still images from the beginning/middle/end of each video. No external API is used, as these images are built into YouTube")
)
),
StringResource("revanced_alt_thumbnails_preference_screen_summary", "Video thumbnail settings")
)
)

MessageDigestImageUrlParentFingerprint.result
?: return MessageDigestImageUrlParentFingerprint.toErrorResult()
MessageDigestImageUrlFingerprint.resolve(context, MessageDigestImageUrlParentFingerprint.result!!.classDef)
MessageDigestImageUrlFingerprint.result?.apply {
loadImageUrlMethod = mutableMethod
} ?: return MessageDigestImageUrlFingerprint.toErrorResult()
addImageUrlHook(INTEGRATIONS_CLASS_DESCRIPTOR, true)


CronetURLRequestCallbackOnResponseStartedFingerprint.result
?: return CronetURLRequestCallbackOnResponseStartedFingerprint.toErrorResult()
CronetURLRequestCallbackOnSucceededFingerprint.resolve(
context,
CronetURLRequestCallbackOnResponseStartedFingerprint.result!!.classDef
)
CronetURLRequestCallbackOnSucceededFingerprint.result?.apply {
loadImageSuccessCallbackMethod = mutableMethod
} ?: return CronetURLRequestCallbackOnSucceededFingerprint.toErrorResult()
addImageUrlSuccessCallbackHook(INTEGRATIONS_CLASS_DESCRIPTOR)


CronetURLRequestCallbackOnFailureFingerprint.resolve(
context,
CronetURLRequestCallbackOnResponseStartedFingerprint.result!!.classDef
)
CronetURLRequestCallbackOnFailureFingerprint.result?.apply {
loadImageErrorCallbackMethod = mutableMethod
} ?: return CronetURLRequestCallbackOnFailureFingerprint.toErrorResult()


return PatchResultSuccess()
}

internal companion object {
private const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/patches/AlternativeThumbnailsPatch;"

private lateinit var loadImageUrlMethod: MutableMethod
private var loadImageUrlIndex = 0

private lateinit var loadImageSuccessCallbackMethod: MutableMethod
private var loadImageSuccessCallbackIndex = 0

private lateinit var loadImageErrorCallbackMethod: MutableMethod
private var loadImageErrorCallbackIndex = 0

/**
* @param highPriority If the hook should be called before all other hooks.
*/
fun addImageUrlHook(targetMethodClass: String, highPriority: Boolean) {
loadImageUrlMethod.addInstructions(
if (highPriority) 0 else loadImageUrlIndex, """
invoke-static { p1 }, $targetMethodClass->overrideImageURL(Ljava/lang/String;)Ljava/lang/String;
move-result-object p1
"""
)
loadImageUrlIndex += 2
}

/**
* If a connection completed, which includes normal 200 responses but also includes
* status 404 and other error like http responses.
*/
fun addImageUrlSuccessCallbackHook(targetMethodClass: String) {
loadImageSuccessCallbackMethod.addInstruction(
loadImageSuccessCallbackIndex++,
"invoke-static { p2 }, $targetMethodClass->handleCronetSuccess(Lorg/chromium/net/UrlResponseInfo;)V"
)
}

/**
* If a connection outright failed to complete any connection.
*/
fun addImageUrlErrorCallbackHook(targetMethodClass: String) {
loadImageErrorCallbackMethod.addInstruction(
loadImageErrorCallbackIndex++,
"invoke-static { p2, p3 }, $targetMethodClass->handleCronetFailure(Lorg/chromium/net/UrlResponseInfo;Ljava/io/IOException;)V"
)
}

}
}

0 comments on commit 8a4277c

Please sign in to comment.