feat(YouTube/Alternative thumbnails): matches official ReVanced code

This commit is contained in:
inotia00 2023-12-12 13:12:12 +09:00
parent 819348054b
commit 752c1df838
11 changed files with 222 additions and 75 deletions

View File

@ -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,
)
) {
override fun execute(context: BytecodeContext) {
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
/**
* Hook should should come first.
* @param highPriority If the hook should be called before all other hooks.
*/
MessageDigestImageUrlParentFingerprint.result?.let { parentResult ->
MessageDigestImageUrlFingerprint.also {
it.resolve(
context,
parentResult.classDef
)
}.result?.let {
it.mutableMethod.apply {
addInstructions(
0, """
invoke-static { p1 }, $ALTERNATIVE_THUMBNAILS->overrideImageURL(Ljava/lang/String;)Ljava/lang/String;
@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
}
} ?: 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"
@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) {
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(
"""
iget-object v0, p0, $definingClass->${urlFieldName}:Ljava/lang/String;
return-object v0
"""
)
}
)
}
} ?: throw CronetURLRequestCallbackOnSucceededFingerprint.exception
} ?: throw CronetURLRequestCallbackOnResponseStartedFingerprint.exception
/**
* Copy arrays

View File

@ -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")
)

View File

@ -4,7 +4,7 @@ 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;",
parameters = emptyList(),

View File

@ -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;"
}

View File

@ -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"
}
)

View File

@ -1,11 +1,11 @@
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;"),

View File

@ -1,10 +1,10 @@
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;"),

View File

@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="revanced_alt_thumbnail_type_entries">
<item>@string/revanced_alt_thumbnail_type_entry_1</item>
<item>@string/revanced_alt_thumbnail_type_entry_2</item>
<item>@string/revanced_alt_thumbnail_type_entry_3</item>
<item>@string/revanced_alt_thumbnail_stills_time_entry_1</item>
<item>@string/revanced_alt_thumbnail_stills_time_entry_2</item>
<item>@string/revanced_alt_thumbnail_stills_time_entry_3</item>
</string-array>
<string-array name="revanced_alt_thumbnail_type_entry_values">
<string-array name="revanced_alt_thumbnail_stills_time_entry_values">
<item>1</item>
<item>2</item>
<item>3</item>

View File

@ -8,21 +8,42 @@
<string name="revanced_ads">Ads</string>
<string name="revanced_alt_thumbnails">Alternative thumbnails</string>
<string name="revanced_alt_thumbnail_about_summary">A feature that replaced default video thumbnails with Alternative ones to reduce sensationalism.</string>
<string name="revanced_alt_thumbnail_about_title">About</string>
<string name="revanced_alt_thumbnail_dearrow_summary_off">Thumbnails is generated without external API.</string>
<string name="revanced_alt_thumbnail_dearrow_summary_on">Thumbnails is generated using DeArrow API.</string>
<string name="revanced_alt_thumbnail_dearrow_title">Using DeArrow API</string>
<string name="revanced_alt_thumbnail_enabled_summary_off">Original YouTube thumbnails shown.</string>
<string name="revanced_alt_thumbnail_enabled_summary_on">YouTube thumbnails replaced with Alternative ones.</string>
<string name="revanced_alt_thumbnail_enabled_title">Enable alternative thumbnails</string>
<string name="revanced_alt_thumbnail_skip_checking_summary_off">Thumbnails will be checked if exist or not before replacing.</string>
<string name="revanced_alt_thumbnail_skip_checking_summary_on">Thumbnails will load faster, but some video will show blank thumbnails.</string>
<string name="revanced_alt_thumbnail_skip_checking_title">Skip checking for thumbnails</string>
<string name="revanced_alt_thumbnail_type_entry_1">Beginning of video</string>
<string name="revanced_alt_thumbnail_type_entry_2">Middle of video</string>
<string name="revanced_alt_thumbnail_type_entry_3">End of video</string>
<string name="revanced_alt_thumbnail_type_title">Video time to take the still from</string>
<string name="revanced_alt_thumbnail_about_status_dearrow">"Showing DeArrow thumbnails.
If a video has no DeArrow thumbnails then the original YouTube thumbnails are shown."</string>
<string name="revanced_alt_thumbnail_about_status_dearrow_stills">"Showing DeArrow thumbnails.
If a video has no DeArrow thumbnails then still video captures are shown."</string>
<string name="revanced_alt_thumbnail_about_status_disabled">Showing original YouTube thumbnails.</string>
<string name="revanced_alt_thumbnail_about_status_stills">Showing still video captures.</string>
<string name="revanced_alt_thumbnail_about_title">Thumbnails in use</string>
<string name="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.
Tap here to learn more about DeArrow."</string>
<string name="revanced_alt_thumbnail_dearrow_about_title">About DeArrow</string>
<string name="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."</string>
<string name="revanced_alt_thumbnail_dearrow_api_url_title">DeArrow API endpoint</string>
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_off">Toast not shown if DeArrow is not available.</string>
<string name="revanced_alt_thumbnail_dearrow_connection_toast_summary_on">Toast shown if DeArrow is not available.</string>
<string name="revanced_alt_thumbnail_dearrow_connection_toast_title">Show toast if API is not available</string>
<string name="revanced_alt_thumbnail_dearrow_error">DeArrow temporarily not available. (status code: %s)</string>
<string name="revanced_alt_thumbnail_dearrow_error_generic">DeArrow temporarily not available.</string>
<string name="revanced_alt_thumbnail_dearrow_summary_off">Not using DeArrow.</string>
<string name="revanced_alt_thumbnail_dearrow_summary_on">Using DeArrow.</string>
<string name="revanced_alt_thumbnail_dearrow_title">Enable DeArrow</string>
<string name="revanced_alt_thumbnail_preference_screen_summary">Video thumbnail settings.</string>
<string name="revanced_alt_thumbnail_preference_screen_title">Alternative thumbnails</string>
<string name="revanced_alt_thumbnail_stills">Still video captures</string>
<string name="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.</string>
<string name="revanced_alt_thumbnail_stills_about_title">About still video captures</string>
<string name="revanced_alt_thumbnail_stills_fast_summary_off">Using high quality still captures.</string>
<string name="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.</string>
<string name="revanced_alt_thumbnail_stills_fast_title">Use fast still captures</string>
<string name="revanced_alt_thumbnail_stills_summary_off">Not using YouTube video still captures.</string>
<string name="revanced_alt_thumbnail_stills_summary_on">Using YouTube video still captures.</string>
<string name="revanced_alt_thumbnail_stills_time_entry_1">Beginning of video</string>
<string name="revanced_alt_thumbnail_stills_time_entry_2">Middle of video</string>
<string name="revanced_alt_thumbnail_stills_time_entry_3">End of video</string>
<string name="revanced_alt_thumbnail_stills_time_title">Video time to take the still from</string>
<string name="revanced_alt_thumbnail_stills_title">Enable still video captures</string>
<string name="revanced_append_time_stamp_information_summary_off">Append time stamp information is disabled.</string>
<string name="revanced_append_time_stamp_information_summary_on">Append time stamp information is enabled.</string>
<string name="revanced_append_time_stamp_information_title">Append time stamp information</string>

View File

@ -5,6 +5,8 @@
<string name="pref_app_language_dialog_message_no_lang_name">This will change the language used in the app including buttons, text and dialogs, but will not change the language of ReVanced Extended settings.</string>
<string name="pref_app_language_system_default">System default</string>
<string name="revanced_alt_thumbnail_dearrow">DeArrow</string>
<string name="revanced_extended_settings_title">ReVanced Extended</string>
<string name="revanced_hide_account_menu_filter_strings_summary">@string/revanced_custom_filter_strings_summary</string>

View File

@ -32,11 +32,19 @@
<!-- PREFERENCE: ALTERNATIVE_THUMBNAILS_SETTINGS
<PreferenceScreen android:title="@string/revanced_alt_thumbnails" android:key="alt_thumbnails">
<Preference android:title=" " android:selectable="false" android:summary="@string/revanced_alt_thumbnails" />
<SwitchPreference android:title="@string/revanced_alt_thumbnail_enabled_title" android:key="revanced_alt_thumbnail_enabled" android:defaultValue="false" android:summaryOn="@string/revanced_alt_thumbnail_enabled_summary_on" android:summaryOff="@string/revanced_alt_thumbnail_enabled_summary_off" />
<SwitchPreference android:title="@string/revanced_alt_thumbnail_dearrow_title" android:key="revanced_alt_thumbnail_dearrow" android:defaultValue="false" android:summaryOn="@string/revanced_alt_thumbnail_dearrow_summary_on" android:summaryOff="@string/revanced_alt_thumbnail_dearrow_summary_off" />
<ListPreference android:title="@string/revanced_alt_thumbnail_type_title" android:key="revanced_alt_thumbnail_type" android:entries="@array/revanced_alt_thumbnail_type_entries" android:defaultValue="1" android:entryValues="@array/revanced_alt_thumbnail_type_entry_values" />
<SwitchPreference android:title="@string/revanced_alt_thumbnail_skip_checking_title" android:key="revanced_alt_thumbnail_skip_checking" android:defaultValue="false" android:summaryOn="@string/revanced_alt_thumbnail_skip_checking_summary_on" android:summaryOff="@string/revanced_alt_thumbnail_skip_checking_summary_off" />
<Preference android:title="@string/revanced_alt_thumbnail_about_title" android:selectable="false" android:summary="@string/revanced_alt_thumbnail_about_summary" />
<app.revanced.integrations.settingsmenu.AlternativeThumbnailsStatusPreference android:title="@string/revanced_alt_thumbnail_about_title" />
<Preference android:title=" " android:selectable="false" android:summary="@string/revanced_alt_thumbnail_dearrow" />
<SwitchPreference android:title="@string/revanced_alt_thumbnail_dearrow_title" android:key="revanced_alt_thumbnail_dearrow" android:summaryOn="@string/revanced_alt_thumbnail_dearrow_summary_on" android:summaryOff="@string/revanced_alt_thumbnail_dearrow_summary_off" />
<SwitchPreference android:title="@string/revanced_alt_thumbnail_dearrow_connection_toast_title" android:key="revanced_alt_thumbnail_dearrow_connection_toast" android:summaryOn="@string/revanced_alt_thumbnail_dearrow_connection_toast_summary_on" android:summaryOff="@string/revanced_alt_thumbnail_dearrow_connection_toast_summary_off" />
<app.revanced.integrations.settingsmenu.ResettableEditTextPreference android:title="@string/revanced_alt_thumbnail_dearrow_api_url_title" android:key="revanced_alt_thumbnail_dearrow_api_url" android:summary="@string/revanced_alt_thumbnail_dearrow_api_url_summary" android:inputType="text" />
<app.revanced.integrations.settingsmenu.AlternativeThumbnailsAboutDeArrowPreference android:title="@string/revanced_alt_thumbnail_dearrow_about_title" android:selectable="true" android:summary="@string/revanced_alt_thumbnail_dearrow_about_summary" />
<Preference android:title=" " android:selectable="false" android:summary="@string/revanced_alt_thumbnail_stills" />
<SwitchPreference android:title="@string/revanced_alt_thumbnail_stills_title" android:key="revanced_alt_thumbnail_stills" android:summaryOn="@string/revanced_alt_thumbnail_stills_summary_on" android:summaryOff="@string/revanced_alt_thumbnail_stills_summary_off" />
<ListPreference android:entries="@array/revanced_alt_thumbnail_type_entries" android:title="@string/revanced_alt_thumbnail_stills_time_title" android:key="revanced_alt_thumbnail_stills_time" android:entryValues="@array/revanced_alt_thumbnail_stills_time_entry_values" />
<SwitchPreference android:title="@string/revanced_alt_thumbnail_stills_fast_title" android:key="revanced_alt_thumbnail_stills_fast" android:summaryOn="@string/revanced_alt_thumbnail_stills_fast_summary_on" android:summaryOff="@string/revanced_alt_thumbnail_stills_fast_summary_off" />
<Preference android:title="@string/revanced_alt_thumbnail_stills_about_title" android:selectable="true" android:summary="@string/revanced_alt_thumbnail_stills_about_summary" />
</PreferenceScreen>PREFERENCE: ALTERNATIVE_THUMBNAILS_SETTINGS -->