diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/AlternativeThumbnailsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/AlternativeThumbnailsPatch.kt index 31006668a..5a11f9227 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/AlternativeThumbnailsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/AlternativeThumbnailsPatch.kt @@ -3,18 +3,30 @@ package app.revanced.patches.youtube.alternativethumbnails.general import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstructions +import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch -import app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.CronetURLRequestCallbackOnResponseStartedFingerprint -import app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.CronetURLRequestCallbackOnSucceededFingerprint +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.MessageDigestImageUrlFingerprint import app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.MessageDigestImageUrlParentFingerprint +import app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.RequestFingerprint +import app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.request.callback.OnFailureFingerprint +import app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.request.callback.OnResponseStartedFingerprint +import app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.request.callback.OnSucceededFingerprint import app.revanced.patches.youtube.utils.integrations.Constants.ALTERNATIVE_THUMBNAILS import app.revanced.patches.youtube.utils.settings.SettingsPatch import app.revanced.patches.youtube.utils.settings.SettingsPatch.contexts import app.revanced.util.copyXmlNode import app.revanced.util.exception +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod @Patch( name = "Alternative thumbnails", @@ -50,53 +62,123 @@ import app.revanced.util.exception @Suppress("unused") object AlternativeThumbnailsPatch : BytecodePatch( setOf( - CronetURLRequestCallbackOnResponseStartedFingerprint, - MessageDigestImageUrlParentFingerprint + MessageDigestImageUrlParentFingerprint, + OnResponseStartedFingerprint, + RequestFingerprint, ) ) { + 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. + */ + @Suppress("SameParameterValue") + private 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. + */ + @Suppress("SameParameterValue") + private fun addImageUrlSuccessCallbackHook(targetMethodClass: String) { + loadImageSuccessCallbackMethod.addInstruction( + loadImageSuccessCallbackIndex++, + "invoke-static { p1, p2 }, $targetMethodClass->handleCronetSuccess(" + + "Lorg/chromium/net/UrlRequest;Lorg/chromium/net/UrlResponseInfo;)V" + ) + } + + /** + * If a connection outright failed to complete any connection. + */ + @Suppress("SameParameterValue") + private fun addImageUrlErrorCallbackHook(targetMethodClass: String) { + loadImageErrorCallbackMethod.addInstruction( + loadImageErrorCallbackIndex++, + "invoke-static { p1, p2, p3 }, $targetMethodClass->handleCronetFailure(" + + "Lorg/chromium/net/UrlRequest;Lorg/chromium/net/UrlResponseInfo;Ljava/io/IOException;)V" + ) + } + override fun execute(context: BytecodeContext) { - /** - * Hook should should come first. - */ - MessageDigestImageUrlParentFingerprint.result?.let { parentResult -> - MessageDigestImageUrlFingerprint.also { - it.resolve( - context, - parentResult.classDef - ) - }.result?.let { - it.mutableMethod.apply { + fun MethodFingerprint.getResultOrThrow() = + result ?: throw exception + + fun MethodFingerprint.alsoResolve(fingerprint: MethodFingerprint) = + also { resolve(context, fingerprint.getResultOrThrow().classDef) }.getResultOrThrow() + + fun MethodFingerprint.resolveAndLetMutableMethod( + fingerprint: MethodFingerprint, + block: (MutableMethod) -> Unit + ) = alsoResolve(fingerprint).also { block(it.mutableMethod) } + + MessageDigestImageUrlFingerprint.resolveAndLetMutableMethod(MessageDigestImageUrlParentFingerprint) { + loadImageUrlMethod = it + addImageUrlHook(ALTERNATIVE_THUMBNAILS, true) + } + + OnSucceededFingerprint.resolveAndLetMutableMethod(OnResponseStartedFingerprint) { + loadImageSuccessCallbackMethod = it + addImageUrlSuccessCallbackHook(ALTERNATIVE_THUMBNAILS) + } + + OnFailureFingerprint.resolveAndLetMutableMethod(OnResponseStartedFingerprint) { + loadImageErrorCallbackMethod = it + addImageUrlErrorCallbackHook(ALTERNATIVE_THUMBNAILS) + } + + // The URL is required for the failure callback hook, but the URL field is obfuscated. + // Add a helper get method that returns the URL field. + RequestFingerprint.getResultOrThrow().apply { + // The url is the only string field that is set inside the constructor. + val urlFieldInstruction = mutableMethod.getInstructions().first { + if (it.opcode != Opcode.IPUT_OBJECT) + return@first false + + val reference = (it as ReferenceInstruction).reference as FieldReference + reference.type == "Ljava/lang/String;" + } as ReferenceInstruction + + val urlFieldName = (urlFieldInstruction.reference as FieldReference).name + val definingClass = RequestFingerprint.IMPLEMENTATION_CLASS_NAME + val addedMethodName = "getHookedUrl" + mutableClass.methods.add( + ImmutableMethod( + definingClass, + addedMethodName, + emptyList(), + "Ljava/lang/String;", + AccessFlags.PUBLIC.value, + null, + null, + MutableMethodImplementation(2) + ).toMutable().apply { addInstructions( - 0, """ - invoke-static { p1 }, $ALTERNATIVE_THUMBNAILS->overrideImageURL(Ljava/lang/String;)Ljava/lang/String; - move-result-object p1 + """ + iget-object v0, p0, $definingClass->${urlFieldName}:Ljava/lang/String; + return-object v0 """ ) } - } ?: throw MessageDigestImageUrlFingerprint.exception - } ?: throw MessageDigestImageUrlParentFingerprint.exception - - - /** - * If a connection completed, which includes normal 200 responses but also includes - * status 404 and other error like http responses. - */ - CronetURLRequestCallbackOnResponseStartedFingerprint.result?.let { parentResult -> - CronetURLRequestCallbackOnSucceededFingerprint.also { - it.resolve( - context, - parentResult.classDef - ) - }.result?.let { - it.mutableMethod.apply { - addInstruction( - 0, - "invoke-static { p2 }, $ALTERNATIVE_THUMBNAILS->handleCronetSuccess(Lorg/chromium/net/UrlResponseInfo;)V" - ) - } - } ?: throw CronetURLRequestCallbackOnSucceededFingerprint.exception - } ?: throw CronetURLRequestCallbackOnResponseStartedFingerprint.exception + ) + } /** * Copy arrays diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/MessageDigestImageUrlFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/MessageDigestImageUrlFingerprint.kt index 0b7190b58..0e8d33517 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/MessageDigestImageUrlFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/MessageDigestImageUrlFingerprint.kt @@ -4,7 +4,7 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags -object MessageDigestImageUrlFingerprint : MethodFingerprint( +internal object MessageDigestImageUrlFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, - parameters = listOf("Ljava/lang/String;", "L") + parameters = listOf("Ljava/lang/String;", "L") ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/MessageDigestImageUrlParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/MessageDigestImageUrlParentFingerprint.kt index fe2603f88..a33e6456b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/MessageDigestImageUrlParentFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/MessageDigestImageUrlParentFingerprint.kt @@ -4,9 +4,9 @@ import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags -object MessageDigestImageUrlParentFingerprint : MethodFingerprint( +internal object MessageDigestImageUrlParentFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "Ljava/lang/String;", + returnType = "Ljava/lang/String;", parameters = emptyList(), strings = listOf("@#&=*+-_.,:!?()/~'%;\$"), ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/RequestFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/RequestFingerprint.kt new file mode 100644 index 000000000..b470e6110 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/RequestFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.RequestFingerprint.IMPLEMENTATION_CLASS_NAME +import com.android.tools.smali.dexlib2.AccessFlags + +internal object RequestFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR, + customFingerprint = { _, classDef -> + classDef.type == IMPLEMENTATION_CLASS_NAME + } +) { + const val IMPLEMENTATION_CLASS_NAME = "Lorg/chromium/net/impl/CronetUrlRequest;" +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnFailureFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnFailureFingerprint.kt new file mode 100644 index 000000000..617c75bfd --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnFailureFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.request.callback + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +internal object OnFailureFingerprint : 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" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/CronetURLRequestCallbackOnResponseStartedFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt similarity index 73% rename from src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/CronetURLRequestCallbackOnResponseStartedFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt index 19f417728..703f90f49 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/CronetURLRequestCallbackOnResponseStartedFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt @@ -1,14 +1,14 @@ -package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints +package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.request.callback import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags // Acts as a parent fingerprint. -object CronetURLRequestCallbackOnResponseStartedFingerprint : MethodFingerprint( +internal object OnResponseStartedFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;"), + parameters = listOf("Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;"), strings = listOf( "Content-Length", "Content-Type", diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/CronetURLRequestCallbackOnSucceededFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt similarity index 66% rename from src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/CronetURLRequestCallbackOnSucceededFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt index 0dc4df395..18e2f6436 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/CronetURLRequestCallbackOnSucceededFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt @@ -1,13 +1,13 @@ -package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints +package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.request.callback import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags -object CronetURLRequestCallbackOnSucceededFingerprint : MethodFingerprint( +internal object OnSucceededFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = listOf("Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;"), + parameters = listOf("Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;"), customFingerprint = { methodDef, _ -> methodDef.name == "onSucceeded" } diff --git a/src/main/resources/youtube/alternativethumbnails/host/values/arrays.xml b/src/main/resources/youtube/alternativethumbnails/host/values/arrays.xml index 2acc6eb1e..f2c8ef755 100644 --- a/src/main/resources/youtube/alternativethumbnails/host/values/arrays.xml +++ b/src/main/resources/youtube/alternativethumbnails/host/values/arrays.xml @@ -1,11 +1,11 @@ - @string/revanced_alt_thumbnail_type_entry_1 - @string/revanced_alt_thumbnail_type_entry_2 - @string/revanced_alt_thumbnail_type_entry_3 + @string/revanced_alt_thumbnail_stills_time_entry_1 + @string/revanced_alt_thumbnail_stills_time_entry_2 + @string/revanced_alt_thumbnail_stills_time_entry_3 - + 1 2 3 diff --git a/src/main/resources/youtube/settings/host/values/strings.xml b/src/main/resources/youtube/settings/host/values/strings.xml index 0a66dafc5..944c3e296 100644 --- a/src/main/resources/youtube/settings/host/values/strings.xml +++ b/src/main/resources/youtube/settings/host/values/strings.xml @@ -8,21 +8,42 @@ Ads Alternative thumbnails - A feature that replaced default video thumbnails with Alternative ones to reduce sensationalism. - About - Thumbnails is generated without external API. - Thumbnails is generated using DeArrow API. - Using DeArrow API - Original YouTube thumbnails shown. - YouTube thumbnails replaced with Alternative ones. - Enable alternative thumbnails - Thumbnails will be checked if exist or not before replacing. - Thumbnails will load faster, but some video will show blank thumbnails. - Skip checking for thumbnails - Beginning of video - Middle of video - End of video - Video time to take the still from + "Showing DeArrow thumbnails. +If a video has no DeArrow thumbnails then the original YouTube thumbnails are shown." + "Showing DeArrow thumbnails. +If a video has no DeArrow thumbnails then still video captures are shown." + Showing original YouTube thumbnails. + Showing still video captures. + Thumbnails in use + "DeArrow provides crowd sourced thumbnails for YouTube videos. These thumbnails are often more relevant than those provided by YouTube. If enabled, video URLs will be sent to the API server and no other data is sent. + +Tap here to learn more about DeArrow." + About DeArrow + "The URL of the DeArrow thumbnail cache endpoint. Do not change this unless you know what you're doing." + DeArrow API endpoint + Toast not shown if DeArrow is not available. + Toast shown if DeArrow is not available. + Show toast if API is not available + DeArrow temporarily not available. (status code: %s) + DeArrow temporarily not available. + Not using DeArrow. + Using DeArrow. + Enable DeArrow + Video thumbnail settings. + Alternative thumbnails + Still video captures + Still captures are taken from the beginning / middle / end of each video. These images are built into YouTube and no external API is used. + About still video captures + Using high quality still captures. + Using medium quality still captures. Thumbnails will load faster, but live streams, unreleased, or very old videos may show blank thumbnails. + Use fast still captures + Not using YouTube video still captures. + Using YouTube video still captures. + Beginning of video + Middle of video + End of video + Video time to take the still from + Enable still video captures Append time stamp information is disabled. Append time stamp information is enabled. Append time stamp information diff --git a/src/main/resources/youtube/settings/values-v21/strings.xml b/src/main/resources/youtube/settings/values-v21/strings.xml index 608da1a6a..2131a9afe 100644 --- a/src/main/resources/youtube/settings/values-v21/strings.xml +++ b/src/main/resources/youtube/settings/values-v21/strings.xml @@ -5,6 +5,8 @@ This will change the language used in the app including buttons, text and dialogs, but will not change the language of ReVanced Extended settings. System default + DeArrow + ReVanced Extended @string/revanced_custom_filter_strings_summary diff --git a/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/src/main/resources/youtube/settings/xml/revanced_prefs.xml index d476589c4..56a9016a7 100644 --- a/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -32,11 +32,19 @@