mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-27 20:30:19 +02:00
feat(YouTube): separate the Bypass image region restrictions
patch from the Alternative thumbnails
patch (Reflecting changes in ReVanced)
This commit is contained in:
parent
0309d371f7
commit
d10869a129
@ -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;")
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
@ -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"
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package app.revanced.patches.music.utils.imageurlhook
|
||||
|
||||
import app.revanced.patches.shared.imageurlhook.BaseCronetImageUrlHookPatch
|
||||
|
||||
object CronetImageUrlHookPatch : BaseCronetImageUrlHookPatch(false)
|
@ -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
|
||||
"""
|
||||
)
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
""",
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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")
|
||||
)
|
@ -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("@#&=*+-_.,:!?()/~'%;\$"),
|
||||
)
|
@ -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(
|
@ -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"
|
||||
}
|
@ -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",
|
@ -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"
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
@ -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)
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package app.revanced.patches.youtube.utils.imageurlhook
|
||||
|
||||
import app.revanced.patches.shared.imageurlhook.BaseCronetImageUrlHookPatch
|
||||
|
||||
object CronetImageUrlHookPatch : BaseCronetImageUrlHookPatch(true)
|
@ -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].
|
||||
*
|
||||
|
@ -360,6 +360,8 @@ Some features may not work properly in the old player layout."</string>
|
||||
<!-- PreferenceScreen: Miscellaneous -->
|
||||
<string name="revanced_preference_screen_misc_title">Miscellaneous</string>
|
||||
|
||||
<string name="revanced_bypass_image_region_restrictions_title">Bypass image region restrictions</string>
|
||||
<string name="revanced_bypass_image_region_restrictions_summary">Replaces the domain that is blocked in some regions so that playlist thumbnails, channel avatars, etc. can be received.</string>
|
||||
<string name="revanced_enable_cairo_splash_animation_title">Enable Cairo splash animation</string>
|
||||
<string name="revanced_enable_cairo_splash_animation_summary">Enables Cairo splash animation when the app starts up.</string>
|
||||
<string name="revanced_enable_debug_logging_title">Enable debug logging</string>
|
||||
@ -391,11 +393,6 @@ Tap on the continue button and disable battery optimizations."</string>
|
||||
<string name="revanced_sanitize_sharing_links_title">Sanitize sharing links</string>
|
||||
<string name="revanced_sanitize_sharing_links_summary">Removes tracking query parameters from URLs when sharing links.</string>
|
||||
|
||||
<string name="revanced_use_alternative_domain_title">Use alternative domain for images</string>
|
||||
<string name="revanced_use_alternative_domain_summary">Replaces the domain that is blocked in some regions so that playlist thumbnails, channel avatars, etc. can be received.</string>
|
||||
<string name="revanced_alternative_domain_title">Alternative domain</string>
|
||||
<string name="revanced_alternative_domain_summary">The domain to fetch images from.\nNote: Only enter the domain name, i.e., without the \"https\:\/\/\" prefix.</string>
|
||||
|
||||
<string name="revanced_extended_settings_import_export_title">Import / Export settings</string>
|
||||
<string name="revanced_extended_settings_import_export_summary">Import or export settings.</string>
|
||||
|
||||
|
@ -89,10 +89,12 @@ Tap here to learn more about DeArrow."</string>
|
||||
<string name="revanced_alt_thumbnail_stills_time_entry_3">End of video</string>
|
||||
<string name="revanced_alt_thumbnail_dearrow_error">DeArrow temporarily unavailable. (status code: %s)</string>
|
||||
<string name="revanced_alt_thumbnail_dearrow_error_generic">DeArrow temporarily unavailable.</string>
|
||||
<string name="revanced_alt_thumbnail_use_alternative_domain_title">Use alternative domain for images</string>
|
||||
<string name="revanced_alt_thumbnail_use_alternative_domain_summary">Replaces the domain that is blocked in some regions so that video thumbnails, channel avatars, community post images, etc. can be received.</string>
|
||||
<string name="revanced_alt_thumbnail_alternative_domain_title">Alternative domain</string>
|
||||
<string name="revanced_alt_thumbnail_alternative_domain_summary">The domain to fetch images from.\nNote: Only enter the domain name, i.e., without the \"https\:\/\/\" prefix.</string>
|
||||
|
||||
<!-- PreferenceScreen: Alternative thumbnails, PreferenceCategory: Image region restrictions -->
|
||||
<string name="revanced_preference_category_image_region_restrictions">Image region restrictions</string>
|
||||
<string name="revanced_bypass_image_region_restrictions_title">Bypass image region restrictions</string>
|
||||
<string name="revanced_bypass_image_region_restrictions_summary_on">Using image host yt4.ggpht.com.</string>
|
||||
<string name="revanced_bypass_image_region_restrictions_summary_off">Using original image host.\n\nEnabling this can fix missing images that are blocked in some regions.</string>
|
||||
|
||||
<!-- PreferenceScreen: Feed -->
|
||||
<string name="revanced_preference_screen_feed_title">Feed</string>
|
||||
|
@ -18,7 +18,9 @@
|
||||
|
||||
|
||||
<!-- PREFERENCE_SCREEN: ALTERNATIVE_THUMBNAILS
|
||||
<PreferenceScreen android:title="@string/revanced_preference_screen_alt_thumbnails_title" android:key="revanced_preference_screen_alt_thumbnails">
|
||||
<PreferenceScreen android:title="@string/revanced_preference_screen_alt_thumbnails_title" android:key="revanced_preference_screen_alt_thumbnails">PREFERENCE_SCREEN: ALTERNATIVE_THUMBNAILS -->
|
||||
|
||||
<!-- SETTINGS: ALTERNATIVE_THUMBNAILS
|
||||
<ListPreference android:entries="@array/revanced_alt_thumbnail_options_entries" android:title="@string/revanced_alt_thumbnail_home_title" android:key="revanced_alt_thumbnail_home" android:entryValues="@array/revanced_alt_thumbnail_options_entry_values" />
|
||||
<ListPreference android:entries="@array/revanced_alt_thumbnail_options_entries" android:title="@string/revanced_alt_thumbnail_subscriptions_title" android:key="revanced_alt_thumbnail_subscriptions" android:entryValues="@array/revanced_alt_thumbnail_options_entry_values" />
|
||||
<ListPreference android:entries="@array/revanced_alt_thumbnail_options_entries" android:title="@string/revanced_alt_thumbnail_library_title" android:key="revanced_alt_thumbnail_library" android:entryValues="@array/revanced_alt_thumbnail_options_entry_values" />
|
||||
@ -29,9 +31,13 @@
|
||||
<app.revanced.integrations.shared.settings.preference.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" />
|
||||
<Preference android:title="@string/revanced_alt_thumbnail_stills_about_title" android:selectable="false" android:summary="@string/revanced_alt_thumbnail_stills_about_summary" />
|
||||
<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" />
|
||||
<ListPreference android:entries="@array/revanced_alt_thumbnail_stills_time_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_use_alternative_domain_title" android:key="revanced_alt_thumbnail_use_alternative_domain" android:summary="@string/revanced_alt_thumbnail_use_alternative_domain_summary"/>
|
||||
<app.revanced.integrations.shared.settings.preference.ResettableEditTextPreference android:hint="yt4.ggpht.com" android:title="@string/revanced_alt_thumbnail_alternative_domain_title" android:key="revanced_alt_thumbnail_alternative_domain" android:summary="@string/revanced_alt_thumbnail_alternative_domain_summary" android:inputType="textUri"/>
|
||||
<ListPreference android:entries="@array/revanced_alt_thumbnail_stills_time_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" />SETTINGS: ALTERNATIVE_THUMBNAILS -->
|
||||
|
||||
<!-- SETTINGS: BYPASS_IMAGE_REGION_RESTRICTIONS
|
||||
<PreferenceCategory android:title="@string/revanced_preference_category_experimental_flag" android:layout="@layout/revanced_settings_preferences_category"/>
|
||||
<SwitchPreference android:title="@string/revanced_bypass_image_region_restrictions_title" android:key="revanced_bypass_image_region_restrictions" android:summaryOn="@string/revanced_bypass_image_region_restrictions_summary_on" android:summaryOff="@string/revanced_bypass_image_region_restrictions_summary_off" />SETTINGS: BYPASS_IMAGE_REGION_RESTRICTIONS -->
|
||||
|
||||
<!-- PREFERENCE_SCREEN: ALTERNATIVE_THUMBNAILS
|
||||
</PreferenceScreen>PREFERENCE_SCREEN: ALTERNATIVE_THUMBNAILS -->
|
||||
|
||||
|
||||
@ -659,6 +665,7 @@
|
||||
|
||||
<PreferenceCategory android:title="@string/revanced_preference_screen_alt_thumbnails_title" android:layout="@layout/revanced_settings_preferences_category">
|
||||
<Preference android:title="Alternative thumbnails" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
<Preference android:title="Bypass image region restrictions" android:summary="@string/revanced_patches_excluded" android:selectable="false"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/revanced_preference_screen_feed_title" android:layout="@layout/revanced_settings_preferences_category">
|
||||
|
Loading…
x
Reference in New Issue
Block a user