diff --git a/src/main/kotlin/app/revanced/patches/music/misc/alternativedomain/AlternativeDomainBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/alternativedomain/AlternativeDomainBytecodePatch.kt deleted file mode 100644 index 0e59cd47d..000000000 --- a/src/main/kotlin/app/revanced/patches/music/misc/alternativedomain/AlternativeDomainBytecodePatch.kt +++ /dev/null @@ -1,6 +0,0 @@ -package app.revanced.patches.music.misc.alternativedomain - -import app.revanced.patches.shared.alternativedomain.BaseAlternativeDomainPatch -import app.revanced.patches.music.utils.integrations.Constants.MISC_PATH - -object AlternativeDomainBytecodePatch : BaseAlternativeDomainPatch("$MISC_PATH/AlternativeDomainPatch;") \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/misc/alternativedomain/AlternativeDomainPatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/alternativedomain/AlternativeDomainPatch.kt deleted file mode 100644 index 9e57c97e1..000000000 --- a/src/main/kotlin/app/revanced/patches/music/misc/alternativedomain/AlternativeDomainPatch.kt +++ /dev/null @@ -1,35 +0,0 @@ -package app.revanced.patches.music.misc.alternativedomain - -import app.revanced.patcher.data.BytecodeContext -import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.music.utils.settings.CategoryType -import app.revanced.patches.music.utils.settings.SettingsPatch -import app.revanced.util.patch.BaseBytecodePatch -import java.io.Closeable - -@Suppress("unused") -object AlternativeDomainPatch : BaseBytecodePatch( - name = "Alternative domain", - description = "Adds options to replace static images(avatars, playlist covers, etc.) domain.", - dependencies = setOf( - AlternativeDomainBytecodePatch::class, - SettingsPatch::class - ), - compatiblePackages = COMPATIBLE_PACKAGE -), Closeable { - override fun execute(context: BytecodeContext) { - } - // Use Closeable for lexicographic arrangement of settings. - override fun close() { - SettingsPatch.addSwitchPreference( - CategoryType.MISC, - "revanced_use_alternative_domain", - "false" - ) - SettingsPatch.addPreferenceWithIntent( - CategoryType.MISC, - "revanced_alternative_domain", - "revanced_use_alternative_domain" - ) - } -} diff --git a/src/main/kotlin/app/revanced/patches/music/misc/thumbnails/BypassImageRegionRestrictionsPatch.kt b/src/main/kotlin/app/revanced/patches/music/misc/thumbnails/BypassImageRegionRestrictionsPatch.kt new file mode 100644 index 000000000..c30dd70c3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/misc/thumbnails/BypassImageRegionRestrictionsPatch.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.misc.thumbnails + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.music.utils.imageurlhook.CronetImageUrlHookPatch +import app.revanced.patches.music.utils.settings.CategoryType +import app.revanced.patches.music.utils.settings.SettingsPatch +import app.revanced.util.patch.BaseBytecodePatch + +@Suppress("unused") +object BypassImageRegionRestrictionsPatch : BaseBytecodePatch( + name = "Bypass image region restrictions", + description = "Adds an option to use a different host for static images," + + "and can fix missing images that are blocked in some countries.", + dependencies = setOf( + CronetImageUrlHookPatch::class, + SettingsPatch::class + ), + compatiblePackages = COMPATIBLE_PACKAGE +) { + override fun execute(context: BytecodeContext) { + CronetImageUrlHookPatch.addImageUrlHook() + + SettingsPatch.addSwitchPreference( + CategoryType.MISC, + "revanced_bypass_image_region_restrictions", + "false" + ) + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/utils/imageurlhook/CronetImageUrlHookPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/imageurlhook/CronetImageUrlHookPatch.kt new file mode 100644 index 000000000..bc6364c2e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/imageurlhook/CronetImageUrlHookPatch.kt @@ -0,0 +1,5 @@ +package app.revanced.patches.music.utils.imageurlhook + +import app.revanced.patches.shared.imageurlhook.BaseCronetImageUrlHookPatch + +object CronetImageUrlHookPatch : BaseCronetImageUrlHookPatch(false) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/alternativedomain/BaseAlternativeDomainPatch.kt b/src/main/kotlin/app/revanced/patches/shared/alternativedomain/BaseAlternativeDomainPatch.kt deleted file mode 100644 index 71b7cbe8a..000000000 --- a/src/main/kotlin/app/revanced/patches/shared/alternativedomain/BaseAlternativeDomainPatch.kt +++ /dev/null @@ -1,30 +0,0 @@ -package app.revanced.patches.shared.alternativedomain - -import app.revanced.patcher.data.BytecodeContext -import app.revanced.patcher.extensions.InstructionExtensions.addInstructions -import app.revanced.patcher.patch.BytecodePatch -import app.revanced.patches.shared.alternativedomain.fingerprints.MessageDigestImageUrlFingerprint -import app.revanced.patches.shared.alternativedomain.fingerprints.MessageDigestImageUrlParentFingerprint -import app.revanced.util.resultOrThrow - -abstract class BaseAlternativeDomainPatch( - private val classDescriptor: String -) : BytecodePatch( - setOf(MessageDigestImageUrlParentFingerprint) -) { - override fun execute(context: BytecodeContext) { - - MessageDigestImageUrlFingerprint.resolve( - context, - MessageDigestImageUrlParentFingerprint.resultOrThrow().classDef - ) - - MessageDigestImageUrlFingerprint.resultOrThrow().mutableMethod.addInstructions( - 0, """ - invoke-static { p1 }, $classDescriptor->overrideImageURL(Ljava/lang/String;)Ljava/lang/String; - move-result-object p1 - """ - ) - - } -} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch.kt b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch.kt new file mode 100644 index 000000000..ef52f6478 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/BaseCronetImageUrlHookPatch.kt @@ -0,0 +1,132 @@ +package app.revanced.patches.shared.imageurlhook + +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.patch.BytecodePatch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patches.shared.imageurlhook.fingerprints.MessageDigestImageUrlFingerprint +import app.revanced.patches.shared.imageurlhook.fingerprints.MessageDigestImageUrlParentFingerprint +import app.revanced.patches.shared.imageurlhook.fingerprints.cronet.RequestFingerprint +import app.revanced.patches.shared.imageurlhook.fingerprints.cronet.request.callback.OnFailureFingerprint +import app.revanced.patches.shared.imageurlhook.fingerprints.cronet.request.callback.OnResponseStartedFingerprint +import app.revanced.patches.shared.imageurlhook.fingerprints.cronet.request.callback.OnSucceededFingerprint +import app.revanced.patches.shared.integrations.Constants.PATCHES_PATH +import app.revanced.util.alsoResolve +import app.revanced.util.resultOrThrow +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 + +abstract class BaseCronetImageUrlHookPatch( + private val resolveCronetRequest: Boolean +) : BytecodePatch( + setOf( + MessageDigestImageUrlParentFingerprint, + OnResponseStartedFingerprint, + RequestFingerprint + ) +) { + companion object { + private const val INTEGRATION_SHARED_CLASS_DESCRIPTOR = "$PATCHES_PATH/BypassImageRegionRestrictionsPatch;" + + 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. + */ + internal fun addImageUrlHook(targetMethodClass: String = INTEGRATION_SHARED_CLASS_DESCRIPTOR, highPriority: Boolean = true) { + 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. + */ + internal 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. + */ + internal 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) { + loadImageUrlMethod = MessageDigestImageUrlFingerprint + .alsoResolve(context, MessageDigestImageUrlParentFingerprint).mutableMethod + + if (!resolveCronetRequest) return + + loadImageSuccessCallbackMethod = OnSucceededFingerprint + .alsoResolve(context, OnResponseStartedFingerprint).mutableMethod + + loadImageErrorCallbackMethod = OnFailureFingerprint + .alsoResolve(context, OnResponseStartedFingerprint).mutableMethod + + // 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.resultOrThrow().apply { + // The url is the only string field that is set inside the constructor. + val urlFieldInstruction = mutableMethod.getInstructions().single { + if (it.opcode != Opcode.IPUT_OBJECT) return@single 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 + """, + ) + } + ) + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/shared/alternativedomain/fingerprints/MessageDigestImageUrlFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/MessageDigestImageUrlFingerprint.kt similarity index 71% rename from src/main/kotlin/app/revanced/patches/shared/alternativedomain/fingerprints/MessageDigestImageUrlFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/MessageDigestImageUrlFingerprint.kt index 3f1384d1a..824bd9077 100644 --- a/src/main/kotlin/app/revanced/patches/shared/alternativedomain/fingerprints/MessageDigestImageUrlFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/MessageDigestImageUrlFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.shared.alternativedomain.fingerprints +package app.revanced.patches.shared.imageurlhook.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint @@ -6,5 +6,5 @@ import com.android.tools.smali.dexlib2.AccessFlags 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/shared/alternativedomain/fingerprints/MessageDigestImageUrlParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/MessageDigestImageUrlParentFingerprint.kt similarity index 71% rename from src/main/kotlin/app/revanced/patches/shared/alternativedomain/fingerprints/MessageDigestImageUrlParentFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/MessageDigestImageUrlParentFingerprint.kt index 79def6369..c5d7c1e6a 100644 --- a/src/main/kotlin/app/revanced/patches/shared/alternativedomain/fingerprints/MessageDigestImageUrlParentFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/MessageDigestImageUrlParentFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.shared.alternativedomain.fingerprints +package app.revanced.patches.shared.imageurlhook.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint @@ -6,7 +6,7 @@ import com.android.tools.smali.dexlib2.AccessFlags internal object MessageDigestImageUrlParentFingerprint : MethodFingerprint( accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - returnType = "Ljava/lang/String;", - parameters = emptyList(), + returnType = "Ljava/lang/String;", + parameters = listOf(), 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/shared/imageurlhook/fingerprints/cronet/RequestFingerprint.kt similarity index 68% rename from src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/RequestFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/RequestFingerprint.kt index b470e6110..c9dcdce00 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/RequestFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/RequestFingerprint.kt @@ -1,8 +1,8 @@ -package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet +package app.revanced.patches.shared.imageurlhook.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 app.revanced.patches.shared.imageurlhook.fingerprints.cronet.RequestFingerprint.IMPLEMENTATION_CLASS_NAME import com.android.tools.smali.dexlib2.AccessFlags internal object RequestFingerprint : MethodFingerprint( 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/shared/imageurlhook/fingerprints/cronet/request/callback/OnFailureFingerprint.kt similarity index 58% rename from src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnFailureFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/request/callback/OnFailureFingerprint.kt index d818019d3..050f44b47 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnFailureFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/request/callback/OnFailureFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.request.callback +package app.revanced.patches.shared.imageurlhook.fingerprints.cronet.request.callback import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint @@ -7,11 +7,7 @@ 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;" - ), + parameters = listOf("Lorg/chromium/net/UrlRequest;", "Lorg/chromium/net/UrlResponseInfo;", "Lorg/chromium/net/CronetException;"), customFingerprint = { methodDef, _ -> methodDef.name == "onFailed" } diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt similarity index 73% rename from src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt index 6abcab634..8de8301d0 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/request/callback/OnResponseStartedFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.request.callback +package app.revanced.patches.shared.imageurlhook.fingerprints.cronet.request.callback import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint @@ -8,7 +8,7 @@ import com.android.tools.smali.dexlib2.AccessFlags 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/cronet/request/callback/OnSucceededFingerprint.kt b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt similarity index 65% rename from src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt rename to src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt index ec46b9900..70b291ac9 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/shared/imageurlhook/fingerprints/cronet/request/callback/OnSucceededFingerprint.kt @@ -1,4 +1,4 @@ -package app.revanced.patches.youtube.alternativethumbnails.general.fingerprints.cronet.request.callback +package app.revanced.patches.shared.imageurlhook.fingerprints.cronet.request.callback import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint @@ -7,7 +7,7 @@ import com.android.tools.smali.dexlib2.AccessFlags 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/kotlin/app/revanced/patches/youtube/alternative/thumbnails/AlternativeThumbnailsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/alternative/thumbnails/AlternativeThumbnailsPatch.kt new file mode 100644 index 000000000..10af0c38b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/alternative/thumbnails/AlternativeThumbnailsPatch.kt @@ -0,0 +1,41 @@ +package app.revanced.patches.youtube.alternative.thumbnails + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.imageurlhook.CronetImageUrlHookPatch +import app.revanced.patches.youtube.utils.integrations.Constants.ALTERNATIVE_THUMBNAILS_CLASS_DESCRIPTOR +import app.revanced.patches.youtube.utils.navigation.NavigationBarHookPatch +import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch +import app.revanced.patches.youtube.utils.settings.SettingsPatch +import app.revanced.util.patch.BaseBytecodePatch + +@Suppress("unused") +object AlternativeThumbnailsPatch : BaseBytecodePatch( + name = "Alternative thumbnails", + description = "Adds options to replace video thumbnails using the DeArrow API or image captures from the video.", + dependencies = setOf( + CronetImageUrlHookPatch::class, + NavigationBarHookPatch::class, + PlayerTypeHookPatch::class, + SettingsPatch::class, + ), + compatiblePackages = COMPATIBLE_PACKAGE +) { + override fun execute(context: BytecodeContext) { + CronetImageUrlHookPatch.addImageUrlHook(ALTERNATIVE_THUMBNAILS_CLASS_DESCRIPTOR) + CronetImageUrlHookPatch.addImageUrlSuccessCallbackHook(ALTERNATIVE_THUMBNAILS_CLASS_DESCRIPTOR) + CronetImageUrlHookPatch.addImageUrlErrorCallbackHook(ALTERNATIVE_THUMBNAILS_CLASS_DESCRIPTOR) + + /** + * Add settings + */ + SettingsPatch.addPreference( + arrayOf( + "PREFERENCE_SCREEN: ALTERNATIVE_THUMBNAILS", + "SETTINGS: ALTERNATIVE_THUMBNAILS" + ) + ) + + SettingsPatch.updatePatchStatus(this) + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternative/thumbnails/BypassImageRegionRestrictionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/alternative/thumbnails/BypassImageRegionRestrictionsPatch.kt new file mode 100644 index 000000000..383e8cda0 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/alternative/thumbnails/BypassImageRegionRestrictionsPatch.kt @@ -0,0 +1,35 @@ +package app.revanced.patches.youtube.alternative.thumbnails + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.imageurlhook.CronetImageUrlHookPatch +import app.revanced.patches.youtube.utils.settings.SettingsPatch +import app.revanced.util.patch.BaseBytecodePatch + +@Suppress("unused") +object BypassImageRegionRestrictionsPatch : BaseBytecodePatch( + name = "Bypass image region restrictions", + description = "Adds an option to use a different host for static images," + + "and can fix missing images that are blocked in some countries.", + dependencies = setOf( + CronetImageUrlHookPatch::class, + SettingsPatch::class, + ), + compatiblePackages = COMPATIBLE_PACKAGE +) { + override fun execute(context: BytecodeContext) { + CronetImageUrlHookPatch.addImageUrlHook() + + /** + * Add settings + */ + SettingsPatch.addPreference( + arrayOf( + "PREFERENCE_SCREEN: ALTERNATIVE_THUMBNAILS", + "SETTINGS: BYPASS_IMAGE_REGION_RESTRICTIONS" + ) + ) + + SettingsPatch.updatePatchStatus(this) + } +} diff --git a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/AlternativeDomainBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/AlternativeDomainBytecodePatch.kt deleted file mode 100644 index 76d0f0b58..000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/AlternativeDomainBytecodePatch.kt +++ /dev/null @@ -1,6 +0,0 @@ -package app.revanced.patches.youtube.alternativethumbnails.general - -import app.revanced.patches.shared.alternativedomain.BaseAlternativeDomainPatch -import app.revanced.patches.youtube.utils.integrations.Constants.ALTERNATIVE_THUMBNAILS_CLASS_DESCRIPTOR - -object AlternativeDomainBytecodePatch : BaseAlternativeDomainPatch(ALTERNATIVE_THUMBNAILS_CLASS_DESCRIPTOR) \ No newline at end of file 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 deleted file mode 100644 index ea79f77c0..000000000 --- a/src/main/kotlin/app/revanced/patches/youtube/alternativethumbnails/general/AlternativeThumbnailsPatch.kt +++ /dev/null @@ -1,142 +0,0 @@ -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.util.proxy.mutableTypes.MutableMethod -import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable -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.compatibility.Constants.COMPATIBLE_PACKAGE -import app.revanced.patches.youtube.utils.integrations.Constants.ALTERNATIVE_THUMBNAILS_CLASS_DESCRIPTOR -import app.revanced.patches.youtube.utils.navigation.NavigationBarHookPatch -import app.revanced.patches.youtube.utils.playertype.PlayerTypeHookPatch -import app.revanced.patches.youtube.utils.settings.SettingsPatch -import app.revanced.util.patch.BaseBytecodePatch -import app.revanced.util.resultOrThrow -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 - -@Suppress("unused") -object AlternativeThumbnailsPatch : BaseBytecodePatch( - name = "Alternative thumbnails", - description = "Adds options to replace video thumbnails using the DeArrow API or image captures from the video.", - dependencies = setOf( - AlternativeDomainBytecodePatch::class, - NavigationBarHookPatch::class, - PlayerTypeHookPatch::class, - SettingsPatch::class, - ), - compatiblePackages = COMPATIBLE_PACKAGE, - fingerprints = setOf( - OnResponseStartedFingerprint, - RequestFingerprint, - ) -) { - private lateinit var loadImageSuccessCallbackMethod: MutableMethod - private var loadImageSuccessCallbackIndex = 0 - - private lateinit var loadImageErrorCallbackMethod: MutableMethod - private var loadImageErrorCallbackIndex = 0 - - /** - * 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) { - - fun MethodFingerprint.alsoResolve(fingerprint: MethodFingerprint) = - also { resolve(context, fingerprint.resultOrThrow().classDef) }.resultOrThrow() - - fun MethodFingerprint.resolveAndLetMutableMethod( - fingerprint: MethodFingerprint, - block: (MutableMethod) -> Unit - ) = alsoResolve(fingerprint).also { block(it.mutableMethod) } - - OnSucceededFingerprint.resolveAndLetMutableMethod(OnResponseStartedFingerprint) { - loadImageSuccessCallbackMethod = it - addImageUrlSuccessCallbackHook(ALTERNATIVE_THUMBNAILS_CLASS_DESCRIPTOR) - } - - OnFailureFingerprint.resolveAndLetMutableMethod(OnResponseStartedFingerprint) { - loadImageErrorCallbackMethod = it - addImageUrlErrorCallbackHook(ALTERNATIVE_THUMBNAILS_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.resultOrThrow().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 - """ - ) - } - ) - } - - /** - * Add settings - */ - SettingsPatch.addPreference( - arrayOf( - "PREFERENCE_SCREEN: ALTERNATIVE_THUMBNAILS" - ) - ) - - SettingsPatch.updatePatchStatus(this) - } -} diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/imageurlhook/CronetImageUrlHookPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/imageurlhook/CronetImageUrlHookPatch.kt new file mode 100644 index 000000000..01f39141b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/imageurlhook/CronetImageUrlHookPatch.kt @@ -0,0 +1,5 @@ +package app.revanced.patches.youtube.utils.imageurlhook + +import app.revanced.patches.shared.imageurlhook.BaseCronetImageUrlHookPatch + +object CronetImageUrlHookPatch : BaseCronetImageUrlHookPatch(true) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 3e975f6a2..c156daa32 100644 --- a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -42,6 +42,9 @@ fun MethodFingerprint.resultOrThrow() = result ?: throw exception val MethodFingerprint.exception get() = PatchException("Failed to resolve ${this.javaClass.simpleName}") +fun MethodFingerprint.alsoResolve(context: BytecodeContext, fingerprint: MethodFingerprint) = + also { resolve(context, fingerprint.resultOrThrow().classDef) }.resultOrThrow() + /** * Find the [MutableMethod] from a given [Method] in a [MutableClass]. * diff --git a/src/main/resources/music/settings/host/values/strings.xml b/src/main/resources/music/settings/host/values/strings.xml index 55a3d2c88..6a0faf2bf 100644 --- a/src/main/resources/music/settings/host/values/strings.xml +++ b/src/main/resources/music/settings/host/values/strings.xml @@ -360,6 +360,8 @@ Some features may not work properly in the old player layout." Miscellaneous + Bypass image region restrictions + Replaces the domain that is blocked in some regions so that playlist thumbnails, channel avatars, etc. can be received. Enable Cairo splash animation Enables Cairo splash animation when the app starts up. Enable debug logging @@ -391,11 +393,6 @@ Tap on the continue button and disable battery optimizations." Sanitize sharing links Removes tracking query parameters from URLs when sharing links. - Use alternative domain for images - Replaces the domain that is blocked in some regions so that playlist thumbnails, channel avatars, etc. can be received. - Alternative domain - The domain to fetch images from.\nNote: Only enter the domain name, i.e., without the \"https\:\/\/\" prefix. - Import / Export settings Import or export settings. diff --git a/src/main/resources/youtube/settings/host/values/strings.xml b/src/main/resources/youtube/settings/host/values/strings.xml index eeab0dcd9..f2f1f5adf 100644 --- a/src/main/resources/youtube/settings/host/values/strings.xml +++ b/src/main/resources/youtube/settings/host/values/strings.xml @@ -89,10 +89,12 @@ Tap here to learn more about DeArrow." End of video DeArrow temporarily unavailable. (status code: %s) DeArrow temporarily unavailable. - Use alternative domain for images - Replaces the domain that is blocked in some regions so that video thumbnails, channel avatars, community post images, etc. can be received. - Alternative domain - The domain to fetch images from.\nNote: Only enter the domain name, i.e., without the \"https\:\/\/\" prefix. + + + Image region restrictions + Bypass image region restrictions + Using image host yt4.ggpht.com. + Using original image host.\n\nEnabling this can fix missing images that are blocked in some regions. Feed diff --git a/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/src/main/resources/youtube/settings/xml/revanced_prefs.xml index 51d5bbac2..82b7dafda 100644 --- a/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -18,7 +18,9 @@ + + + + + + @@ -659,6 +665,7 @@ +