mirror of
https://github.com/revanced/revanced-patches
synced 2024-11-09 12:57:09 +01:00
feat(YouTube): Add Alternative thumbnails
patch (#2834)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
parent
c92ed0170f
commit
8a4277c486
@ -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
|
@ -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"
|
||||
}
|
||||
)
|
@ -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"
|
||||
}
|
||||
)
|
@ -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"
|
||||
}
|
||||
)
|
@ -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")
|
||||
)
|
@ -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("@#&=*+-_.,:!?()/~'%;\$"),
|
||||
)
|
@ -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"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user