From 41217f61e600e47dd6812864bff22ee054521d3c Mon Sep 17 00:00:00 2001 From: Chris <52449218+shadow578@users.noreply.github.com> Date: Mon, 11 Dec 2023 02:07:09 +0100 Subject: [PATCH] feat(YouTube - Alternative Thumbnails): Add option to use DeArrow (#3378) Co-authored-by: oSumAtrIX Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> --- api/revanced-patches.api | 5 +- .../settings/preference/BasePreference.kt | 2 +- .../impl/NonInteractivePreference.kt | 12 +- .../thumbnails/AlternativeThumbnailsPatch.kt | 232 +++++++++++++----- .../AlternativeThumbnailsResourcePatch.kt | 16 ++ .../fingerprints/cronet/RequestFingerprint.kt | 16 ++ .../request/callback/OnFailureFingerprint.kt} | 4 +- .../callback/OnResponseStartedFingerprint.kt} | 4 +- .../callback/OnSucceededFingerprint.kt} | 4 +- .../host/values/strings.xml | 9 + 10 files changed, 233 insertions(+), 71 deletions(-) create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsResourcePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/RequestFingerprint.kt rename src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/{CronetURLRequestCallbackOnFailureFingerprint.kt => cronet/request/callback/OnFailureFingerprint.kt} (85%) rename src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/{CronetURLRequestCallbackOnResponseStartedFingerprint.kt => cronet/request/callback/OnResponseStartedFingerprint.kt} (87%) rename src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/{CronetURLRequestCallbackOnSucceededFingerprint.kt => cronet/request/callback/OnSucceededFingerprint.kt} (84%) create mode 100644 src/main/resources/alternativethumbnails/host/values/strings.xml diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 75b395ed..f34e5c55 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -657,7 +657,9 @@ public final class app/revanced/patches/shared/settings/preference/impl/ListPref } public final class app/revanced/patches/shared/settings/preference/impl/NonInteractivePreference : app/revanced/patches/shared/settings/preference/BasePreference { - public fun (Lapp/revanced/patches/shared/settings/preference/impl/StringResource;Lapp/revanced/patches/shared/settings/preference/impl/StringResource;)V + public fun (Lapp/revanced/patches/shared/settings/preference/impl/StringResource;Lapp/revanced/patches/shared/settings/preference/impl/StringResource;Ljava/lang/String;Z)V + public synthetic fun (Lapp/revanced/patches/shared/settings/preference/impl/StringResource;Lapp/revanced/patches/shared/settings/preference/impl/StringResource;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getSelectable ()Z public fun serialize (Lorg/w3c/dom/Document;Lkotlin/jvm/functions/Function1;)Lorg/w3c/dom/Element; } @@ -1363,7 +1365,6 @@ public final class app/revanced/patches/youtube/layout/theme/ThemeBytecodePatch public final class app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch; - public final fun addImageUrlErrorCallbackHook (Ljava/lang/String;)V public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt index 4fc84105..a150aa6f 100644 --- a/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/BasePreference.kt @@ -9,7 +9,7 @@ import org.w3c.dom.Element * * @param key The key of the preference. * @param title The title of the preference. - * @param tag The tag of the preference. + * @param tag The full class name for the preference. * @param summary The summary of the preference. */ abstract class BasePreference( diff --git a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/NonInteractivePreference.kt b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/NonInteractivePreference.kt index eb16067c..ef08bc3e 100644 --- a/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/NonInteractivePreference.kt +++ b/src/main/kotlin/app/revanced/patches/shared/settings/preference/impl/NonInteractivePreference.kt @@ -14,15 +14,21 @@ import org.w3c.dom.Element * * @param title The title of the preference. * @param summary The summary of the text preference. + * @param selectable If this preference responds to tapping. + * Setting to 'true' restores the horizontal dividers on the top and bottom, + * but tapping will still do nothing since this Preference has no key. */ class NonInteractivePreference( title: StringResource, - summary: StringResource, -) : BasePreference(null, title, summary, "Preference") { + summary: StringResource?, + tag: String = "Preference", + // If androidx.preference is later used, this can be changed to the show top/bottom dividers feature. + val selectable: Boolean = false +) : BasePreference(null, title, summary, tag) { override fun serialize(ownerDocument: Document, resourceCallback: (BaseResource) -> Unit): Element { return super.serialize(ownerDocument, resourceCallback).apply { addSummary(summary?.also { resourceCallback.invoke(it) - setAttribute("android:selectable", false.toString()) + setAttribute("android:selectable", selectable.toString()) }) } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt index 27d19981..3ec83edf 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsPatch.kt @@ -1,22 +1,36 @@ package app.revanced.patches.youtube.layout.thumbnails -import app.revanced.util.exception 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.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patches.shared.settings.preference.impl.* -import app.revanced.patches.youtube.layout.thumbnails.fingerprints.* +import app.revanced.patches.youtube.layout.thumbnails.fingerprints.MessageDigestImageUrlFingerprint +import app.revanced.patches.youtube.layout.thumbnails.fingerprints.MessageDigestImageUrlParentFingerprint +import app.revanced.patches.youtube.layout.thumbnails.fingerprints.cronet.RequestFingerprint +import app.revanced.patches.youtube.layout.thumbnails.fingerprints.cronet.request.callback.OnFailureFingerprint +import app.revanced.patches.youtube.layout.thumbnails.fingerprints.cronet.request.callback.OnResponseStartedFingerprint +import app.revanced.patches.youtube.layout.thumbnails.fingerprints.cronet.request.callback.OnSucceededFingerprint import app.revanced.patches.youtube.misc.integrations.IntegrationsPatch import app.revanced.patches.youtube.misc.settings.SettingsPatch +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", description = "Adds options to replace video thumbnails with still image captures of the video.", - dependencies = [IntegrationsPatch::class, SettingsPatch::class], + dependencies = [IntegrationsPatch::class, SettingsPatch::class, AlternativeThumbnailsResourcePatch::class], compatiblePackages = [ CompatiblePackage( "com.google.android.youtube", @@ -34,7 +48,11 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch ) @Suppress("unused") object AlternativeThumbnailsPatch : BytecodePatch( - setOf(MessageDigestImageUrlParentFingerprint, CronetURLRequestCallbackOnResponseStartedFingerprint) + setOf( + MessageDigestImageUrlParentFingerprint, + OnResponseStartedFingerprint, + RequestFingerprint, + ) ) { private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/patches/AlternativeThumbnailsPatch;" @@ -51,11 +69,13 @@ object AlternativeThumbnailsPatch : BytecodePatch( /** * @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 + if (highPriority) 0 else loadImageUrlIndex, + """ + invoke-static { p1 }, $targetMethodClass->overrideImageURL(Ljava/lang/String;)Ljava/lang/String; + move-result-object p1 """ ) loadImageUrlIndex += 2 @@ -65,103 +85,197 @@ object AlternativeThumbnailsPatch : BytecodePatch( * 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 { p2 }, $targetMethodClass->handleCronetSuccess(Lorg/chromium/net/UrlResponseInfo;)V" + "invoke-static { p1, p2 }, $targetMethodClass->handleCronetSuccess(" + + "Lorg/chromium/net/UrlRequest;Lorg/chromium/net/UrlResponseInfo;)V" ) } /** * If a connection outright failed to complete any connection. */ - fun addImageUrlErrorCallbackHook(targetMethodClass: String) { + @Suppress("SameParameterValue") + private fun addImageUrlErrorCallbackHook(targetMethodClass: String) { loadImageErrorCallbackMethod.addInstruction( loadImageErrorCallbackIndex++, - "invoke-static { p2, p3 }, $targetMethodClass->handleCronetFailure(Lorg/chromium/net/UrlResponseInfo;Ljava/io/IOException;)V" + "invoke-static { p1, p2, p3 }, $targetMethodClass->handleCronetFailure(" + + "Lorg/chromium/net/UrlRequest;Lorg/chromium/net/UrlResponseInfo;Ljava/io/IOException;)V" ) } override fun execute(context: BytecodeContext) { SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( PreferenceScreen( - "revanced_alt_thumbnails_preference_screen", - StringResource("revanced_alt_thumbnails_preference_screen_title", "Alternative thumbnails"), + "revanced_alt_thumbnail_preference_screen", + StringResource("revanced_alt_thumbnail_preference_screen_title", "Alternative thumbnails"), listOf( + NonInteractivePreference( + StringResource("revanced_alt_thumbnail_about_title", "Thumbnails in use"), + null, // Summary is dynamically updated based on the current settings. + tag = "app.revanced.integrations.settingsmenu.AlternativeThumbnailsStatusPreference" + ), 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") + "revanced_alt_thumbnail_dearrow", + StringResource("revanced_alt_thumbnail_dearrow_title", "Enable DeArrow"), + StringResource("revanced_alt_thumbnail_dearrow_summary_on", "Using DeArrow"), + StringResource("revanced_alt_thumbnail_dearrow_summary_off", "Not using DeArrow") + ), + SwitchPreference( + "revanced_alt_thumbnail_dearrow_connection_toast", + StringResource("revanced_alt_thumbnail_dearrow_connection_toast_title", "Show toast if API is not available"), + StringResource("revanced_alt_thumbnail_dearrow_connection_toast_summary_on", "Toast shown if DeArrow is not available"), + StringResource("revanced_alt_thumbnail_dearrow_connection_toast_summary_off", "Toast not shown if DeArrow is not available") + ), + TextPreference( + "revanced_alt_thumbnail_dearrow_api_url", + StringResource( + "revanced_alt_thumbnail_dearrow_api_url_title", + "DeArrow API endpoint" + ), + StringResource( + "revanced_alt_thumbnail_dearrow_api_url_summary", + "The URL of the DeArrow thumbnail cache endpoint. " + + "Do not change this unless you know what you\\\'re doing" + ), + ), + NonInteractivePreference( + StringResource( + "revanced_alt_thumbnail_dearrow_about_title", + "About DeArrow" + ), + StringResource( + "revanced_alt_thumbnail_dearrow_about_summary", + "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." + + "\\n\\nTap here to learn more about DeArrow" + ), + // Custom about preference with link to the DeArrow website. + tag = "app.revanced.integrations.settingsmenu.AlternativeThumbnailsAboutDeArrowPreference", + selectable = true + ), + SwitchPreference( + "revanced_alt_thumbnail_stills", + StringResource("revanced_alt_thumbnail_stills_title", "Enable still video captures"), + StringResource("revanced_alt_thumbnail_stills_summary_on", "Using YouTube video still captures"), + StringResource("revanced_alt_thumbnail_stills_summary_off", "Not using YouTube video still captures") ), ListPreference( - "revanced_alt_thumbnail_type", - StringResource("revanced_alt_thumbnail_type_title", "Video time to take the still from"), + "revanced_alt_thumbnail_stills_time", + StringResource("revanced_alt_thumbnail_stills_time_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"), + StringResource("revanced_alt_thumbnail_stills_time_entry_1", "Beginning of video"), + StringResource("revanced_alt_thumbnail_stills_time_entry_2", "Middle of video"), + StringResource("revanced_alt_thumbnail_stills_time_entry_3", "End of video"), ) ), ArrayResource( - "revanced_alt_thumbnail_type_entry_values", + "revanced_alt_thumbnail_stills_time_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"), + StringResource("revanced_alt_thumbnail_stills_time_entry_value_1", "1"), + StringResource("revanced_alt_thumbnail_stills_time_entry_value_2", "2"), + StringResource("revanced_alt_thumbnail_stills_time_entry_value_3", "3"), ) ) ), SwitchPreference( - "revanced_alt_thumbnail_fast_quality", - StringResource("revanced_alt_thumbnail_fast_quality_title", "Use fast alternative thumbnails"), + "revanced_alt_thumbnail_stills_fast", 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" + "revanced_alt_thumbnail_stills_fast_title", + "Use fast still captures" ), - StringResource("revanced_alt_thumbnail_fast_quality_summary_off", "Using high quality stills") + StringResource( + "revanced_alt_thumbnail_stills_fast_summary_on", + "Using medium quality still captures. " + + "Thumbnails will load faster, but live streams, unreleased, " + + "or very old videos may show blank thumbnails" + ), + StringResource( + "revanced_alt_thumbnail_stills_fast_summary_off", + "Using high quality still captures" + ) ), 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" - ) + "revanced_alt_thumbnail_stills_about_title", + "About still video captures" + ), + StringResource( + "revanced_alt_thumbnail_stills_about_summary", + "Still captures are taken from the beginning/middle/end of each video. " + + "These images are built into YouTube and no external API is used" + ), + // Restore the preference dividers to keep it from looking weird. + selectable = true ) ), - StringResource("revanced_alt_thumbnails_preference_screen_summary", "Video thumbnail settings") + StringResource("revanced_alt_thumbnail_preference_screen_summary", "Video thumbnail settings") ) ) - MessageDigestImageUrlParentFingerprint.result - ?: throw MessageDigestImageUrlParentFingerprint.exception - MessageDigestImageUrlFingerprint.resolve(context, MessageDigestImageUrlParentFingerprint.result!!.classDef) - MessageDigestImageUrlFingerprint.result?.apply { - loadImageUrlMethod = mutableMethod - } ?: throw MessageDigestImageUrlFingerprint.exception - addImageUrlHook(INTEGRATIONS_CLASS_DESCRIPTOR, true) + fun MethodFingerprint.getResultOrThrow() = + result ?: throw exception + fun MethodFingerprint.alsoResolve(fingerprint: MethodFingerprint) = + also { resolve(context, fingerprint.getResultOrThrow().classDef) }.getResultOrThrow() - CronetURLRequestCallbackOnResponseStartedFingerprint.result - ?: throw CronetURLRequestCallbackOnResponseStartedFingerprint.exception - CronetURLRequestCallbackOnSucceededFingerprint.resolve( - context, - CronetURLRequestCallbackOnResponseStartedFingerprint.result!!.classDef - ) - CronetURLRequestCallbackOnSucceededFingerprint.result?.apply { - loadImageSuccessCallbackMethod = mutableMethod - } ?: throw CronetURLRequestCallbackOnSucceededFingerprint.exception - addImageUrlSuccessCallbackHook(INTEGRATIONS_CLASS_DESCRIPTOR) + fun MethodFingerprint.resolveAndLetMutableMethod( + fingerprint: MethodFingerprint, + block: (MutableMethod) -> Unit + ) = alsoResolve(fingerprint).also { block(it.mutableMethod) } + MessageDigestImageUrlFingerprint.resolveAndLetMutableMethod(MessageDigestImageUrlParentFingerprint) { + loadImageUrlMethod = it + addImageUrlHook(INTEGRATIONS_CLASS_DESCRIPTOR, true) + } - CronetURLRequestCallbackOnFailureFingerprint.resolve( - context, - CronetURLRequestCallbackOnResponseStartedFingerprint.result!!.classDef - ) - CronetURLRequestCallbackOnFailureFingerprint.result?.apply { - loadImageErrorCallbackMethod = mutableMethod - } ?: throw CronetURLRequestCallbackOnFailureFingerprint.exception + OnSucceededFingerprint.resolveAndLetMutableMethod(OnResponseStartedFingerprint) { + loadImageSuccessCallbackMethod = it + addImageUrlSuccessCallbackHook(INTEGRATIONS_CLASS_DESCRIPTOR) + } + + OnFailureFingerprint.resolveAndLetMutableMethod(OnResponseStartedFingerprint) { + loadImageErrorCallbackMethod = it + addImageUrlErrorCallbackHook(INTEGRATIONS_CLASS_DESCRIPTOR) + } + + // 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( + """ + iget-object v0, p0, $definingClass->${urlFieldName}:Ljava/lang/String; + return-object v0 + """ + ) + }) + } } } diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsResourcePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsResourcePatch.kt new file mode 100644 index 00000000..080b2bec --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/AlternativeThumbnailsResourcePatch.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.thumbnails + +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patches.youtube.misc.settings.SettingsPatch +import app.revanced.util.mergeStrings + +@Patch( + dependencies = [SettingsPatch::class] +) +internal object AlternativeThumbnailsResourcePatch : ResourcePatch() { + override fun execute(context: ResourceContext) { + context.mergeStrings("alternativethumbnails/host/values/strings.xml") + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/RequestFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/RequestFingerprint.kt new file mode 100644 index 00000000..b66ab597 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/RequestFingerprint.kt @@ -0,0 +1,16 @@ +package app.revanced.patches.youtube.layout.thumbnails.fingerprints.cronet + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.youtube.layout.thumbnails.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/layout/thumbnails/fingerprints/CronetURLRequestCallbackOnFailureFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/request/callback/OnFailureFingerprint.kt similarity index 85% rename from src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/CronetURLRequestCallbackOnFailureFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/request/callback/OnFailureFingerprint.kt index 7780c133..5848810e 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/CronetURLRequestCallbackOnFailureFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/request/callback/OnFailureFingerprint.kt @@ -1,10 +1,10 @@ -package app.revanced.patches.youtube.layout.thumbnails.fingerprints +package app.revanced.patches.youtube.layout.thumbnails.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 CronetURLRequestCallbackOnFailureFingerprint : MethodFingerprint( +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;"), diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/CronetURLRequestCallbackOnResponseStartedFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt similarity index 87% rename from src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/CronetURLRequestCallbackOnResponseStartedFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt index b53275d5..1021d243 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/CronetURLRequestCallbackOnResponseStartedFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt @@ -1,11 +1,11 @@ -package app.revanced.patches.youtube.layout.thumbnails.fingerprints +package app.revanced.patches.youtube.layout.thumbnails.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. -internal 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;"), diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/CronetURLRequestCallbackOnSucceededFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt similarity index 84% rename from src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/CronetURLRequestCallbackOnSucceededFingerprint.kt rename to src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt index eb0a1a7d..ed8523c8 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/CronetURLRequestCallbackOnSucceededFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/thumbnails/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt @@ -1,10 +1,10 @@ -package app.revanced.patches.youtube.layout.thumbnails.fingerprints +package app.revanced.patches.youtube.layout.thumbnails.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 CronetURLRequestCallbackOnSucceededFingerprint : MethodFingerprint( +internal object OnSucceededFingerprint : MethodFingerprint( returnType = "V", accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, parameters = listOf("Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;"), diff --git a/src/main/resources/alternativethumbnails/host/values/strings.xml b/src/main/resources/alternativethumbnails/host/values/strings.xml new file mode 100644 index 00000000..272bbc2f --- /dev/null +++ b/src/main/resources/alternativethumbnails/host/values/strings.xml @@ -0,0 +1,9 @@ + + Showing original YouTube thumbnails + Showing still video captures + 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 + + DeArrow temporarily not available (status code: %s) + DeArrow temporarily not available + \ No newline at end of file