feat(YouTube): Support version 18.32.39

This commit is contained in:
oSumAtrIX 2023-08-25 03:00:43 +02:00
parent 5afb63e052
commit 7b503e2336
18 changed files with 149 additions and 185 deletions

View file

@ -15,7 +15,7 @@ import java.util.*
@Name("Spoof wifi connection") @Name("Spoof wifi connection")
@Description("Spoofs an existing Wi-Fi connection.") @Description("Spoofs an existing Wi-Fi connection.")
@RequiresIntegrations @RequiresIntegrations
internal class SpoofWifiPatch : AbstractTransformInstructionsPatch<Instruction35cInfo>() { class SpoofWifiPatch : AbstractTransformInstructionsPatch<Instruction35cInfo>() {
private companion object { private companion object {
const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX = "Lapp/revanced/all/connectivity/wifi/spoof/SpoofWifiPatch" const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX = "Lapp/revanced/all/connectivity/wifi/spoof/SpoofWifiPatch"

View file

@ -7,7 +7,10 @@ import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.annotations.RequiresIntegrations import app.revanced.patcher.patch.annotations.RequiresIntegrations
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.all.screencapture.removerestriction.resource.patch.RemoveCaptureRestrictionResourcePatch import app.revanced.patches.all.screencapture.removerestriction.resource.patch.RemoveCaptureRestrictionResourcePatch
import app.revanced.util.patch.* import app.revanced.util.patch.AbstractTransformInstructionsPatch
import app.revanced.util.patch.IMethodCall
import app.revanced.util.patch.Instruction35cInfo
import app.revanced.util.patch.filterMapInstruction35c
import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.Instruction
@ -17,7 +20,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.Instruction
@Description("Removes the restriction of capturing audio from apps that normally wouldn't allow it.") @Description("Removes the restriction of capturing audio from apps that normally wouldn't allow it.")
@DependsOn([RemoveCaptureRestrictionResourcePatch::class]) @DependsOn([RemoveCaptureRestrictionResourcePatch::class])
@RequiresIntegrations @RequiresIntegrations
internal class RemoveCaptureRestrictionPatch : AbstractTransformInstructionsPatch<Instruction35cInfo>() { class RemoveCaptureRestrictionPatch : AbstractTransformInstructionsPatch<Instruction35cInfo>() {
// Information about method calls we want to replace // Information about method calls we want to replace
enum class MethodCall( enum class MethodCall(
override val definedClassName: String, override val definedClassName: String,

View file

@ -5,17 +5,19 @@ import app.revanced.patcher.annotation.Name
import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.annotations.RequiresIntegrations import app.revanced.patcher.patch.annotations.RequiresIntegrations
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.util.patch.* import app.revanced.util.patch.AbstractTransformInstructionsPatch
import app.revanced.util.patch.IMethodCall
import app.revanced.util.patch.Instruction35cInfo
import app.revanced.util.patch.filterMapInstruction35c
import com.android.tools.smali.dexlib2.iface.ClassDef import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import java.util.*
@Patch(false) @Patch(false)
@Name("Remove screenshot restriction") @Name("Remove screenshot restriction")
@Description("Removes the restriction of taking screenshots in apps that normally wouldn't allow it.") @Description("Removes the restriction of taking screenshots in apps that normally wouldn't allow it.")
@RequiresIntegrations @RequiresIntegrations
internal class RemoveScreenshotRestrictionPatch : AbstractTransformInstructionsPatch<Instruction35cInfo>() { class RemoveScreenshotRestrictionPatch : AbstractTransformInstructionsPatch<Instruction35cInfo>() {
private companion object { private companion object {
const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX = const val INTEGRATIONS_CLASS_DESCRIPTOR_PREFIX =

View file

@ -1,9 +0,0 @@
package app.revanced.patches.youtube.layout.hide.general.fingerprints
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
object ConvertElementToFlatBufferFingerprint : MethodFingerprint(
strings = listOf("Failed to convert Element to Flatbuffers: %s"),
opcodes = listOf(Opcode.IGET_OBJECT) // Patched at this opcodes index
)

View file

@ -0,0 +1,10 @@
package app.revanced.patches.youtube.layout.hide.general.fingerprints
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.Opcode
object ParseElementFromBufferFingerprint : MethodFingerprint(
parameters = listOf("L","L","[B", "L","L"),
opcodes = listOf(Opcode.INVOKE_INTERFACE, Opcode.MOVE_RESULT_OBJECT),
strings = listOf("Failed to parse Element")
)

View file

@ -4,9 +4,9 @@ import app.revanced.extensions.exception
import app.revanced.patcher.annotation.Description import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Name
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstructions
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotations.DependsOn import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch import app.revanced.patcher.patch.annotations.Patch
@ -15,10 +15,12 @@ import app.revanced.patches.shared.settings.preference.impl.StringResource
import app.revanced.patches.shared.settings.preference.impl.SwitchPreference import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
import app.revanced.patches.shared.settings.preference.impl.TextPreference import app.revanced.patches.shared.settings.preference.impl.TextPreference
import app.revanced.patches.youtube.layout.hide.general.annotations.HideLayoutComponentsCompatibility import app.revanced.patches.youtube.layout.hide.general.annotations.HideLayoutComponentsCompatibility
import app.revanced.patches.youtube.layout.hide.general.fingerprints.ConvertElementToFlatBufferFingerprint import app.revanced.patches.youtube.layout.hide.general.fingerprints.ParseElementFromBufferFingerprint
import app.revanced.patches.youtube.misc.litho.filter.patch.LithoFilterPatch import app.revanced.patches.youtube.misc.litho.filter.patch.LithoFilterPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch.PreferenceScreen import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch.PreferenceScreen
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
@Patch @Patch
@Name("Hide layout components") @Name("Hide layout components")
@ -26,7 +28,7 @@ import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch.P
@DependsOn([LithoFilterPatch::class, SettingsPatch::class]) @DependsOn([LithoFilterPatch::class, SettingsPatch::class])
@HideLayoutComponentsCompatibility @HideLayoutComponentsCompatibility
class HideLayoutComponentsPatch : BytecodePatch( class HideLayoutComponentsPatch : BytecodePatch(
listOf(ConvertElementToFlatBufferFingerprint) listOf(ParseElementFromBufferFingerprint)
) { ) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
PreferenceScreen.LAYOUT.addPreferences( PreferenceScreen.LAYOUT.addPreferences(
@ -249,30 +251,26 @@ class HideLayoutComponentsPatch : BytecodePatch(
// region Mix playlists // region Mix playlists
ConvertElementToFlatBufferFingerprint.result?.let { ParseElementFromBufferFingerprint.result?.let { result ->
val returnEmptyComponentIndex = it.scanResult.stringsScanResult!!.matches.first().index + 2 val returnEmptyComponentInstruction = result.mutableMethod.getInstructions()
.last { it.opcode == Opcode.INVOKE_STATIC }
it.mutableMethod.apply { result.mutableMethod.apply {
// The last virtual register (not parameter). Used to store the byte array val consumeByteBufferIndex = result.scanResult.patternScanResult!!.startIndex
// that may contain information about a mix playlist. val byteBufferRegister = getInstruction<FiveRegisterInstruction>(consumeByteBufferIndex).registerD
val freeRegister = (implementation!!.registerCount - 1) - parameterTypes.size - 1
// Check if the byte array contains anything about a mix playlist.
addInstructionsWithLabels( addInstructionsWithLabels(
it.scanResult.patternScanResult!!.startIndex, result.scanResult.patternScanResult!!.startIndex,
""" """
invoke-static {v$freeRegister}, $FILTER_CLASS_DESCRIPTOR->filterMixPlaylists([B)Z invoke-static {v$byteBufferRegister}, $FILTER_CLASS_DESCRIPTOR->filterMixPlaylists([B)Z
move-result v$freeRegister move-result v0 # Conveniently same register happens to be free.
if-nez v$freeRegister, :return_empty_component if-nez v0, :return_empty_component
""", """,
ExternalLabel("return_empty_component", getInstruction(returnEmptyComponentIndex)) ExternalLabel("return_empty_component", returnEmptyComponentInstruction)
) )
// Move the byte array to a free register.
addInstruction(0, "move-object/from16 v$freeRegister, p3")
} }
} ?: throw ConvertElementToFlatBufferFingerprint.exception } ?: throw ParseElementFromBufferFingerprint.exception
// endregion // endregion
} }

View file

@ -1,13 +1,13 @@
package app.revanced.patches.youtube.layout.hide.shorts.bytecode.fingerprints package app.revanced.patches.youtube.layout.hide.shorts.bytecode.fingerprints
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.util.patch.LiteralValueFingerprint
import app.revanced.patches.youtube.layout.hide.shorts.resource.patch.HideShortsComponentsResourcePatch import app.revanced.patches.youtube.layout.hide.shorts.resource.patch.HideShortsComponentsResourcePatch
import app.revanced.util.patch.LiteralValueFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
object CreateShortsButtonsFingerprint : LiteralValueFingerprint( object CreateShortsButtonsFingerprint : LiteralValueFingerprint(
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL, accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
returnType = "V", returnType = "V",
parameters = listOf("Z", "Z", "L"), parameters = listOf("Z", "Z", "L"),
literal = HideShortsComponentsResourcePatch.reelPlayerRightLargeIconSize literal = HideShortsComponentsResourcePatch.reelPlayerRightCellButtonHeight
) )

View file

@ -91,11 +91,11 @@ class HideShortsComponentsResourcePatch : ResourcePatch {
fun String.getId() = ResourceMappingPatch.resourceMappings.single { it.name == this }.id fun String.getId() = ResourceMappingPatch.resourceMappings.single { it.name == this }.id
reelMultipleItemShelfId = "reel_multiple_items_shelf".getId() reelMultipleItemShelfId = "reel_multiple_items_shelf".getId()
reelPlayerRightLargeIconSize = "reel_player_right_large_icon_size".getId() reelPlayerRightCellButtonHeight = "reel_player_right_cell_button_height".getId()
} }
companion object { companion object {
var reelMultipleItemShelfId: Long = -1 var reelMultipleItemShelfId = -1L
var reelPlayerRightLargeIconSize = -1L var reelPlayerRightCellButtonHeight = -1L
} }
} }

View file

@ -13,19 +13,17 @@ object TextComponentAtomicReferenceFingerprint : MethodFingerprint(
accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL, accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL,
parameters = listOf("L"), parameters = listOf("L"),
opcodes = listOf( opcodes = listOf(
Opcode.MOVE_OBJECT_FROM16, // available unused register Opcode.MOVE_OBJECT, // Register A and B is context, use B as context, reuse A as free register
Opcode.MOVE_OBJECT_FROM16, Opcode.INVOKE_VIRTUAL, // Register C is atomic reference
null, // move-object/from16 or move/from16 Opcode.MOVE_RESULT_OBJECT, // Register A is char sequence
Opcode.MOVE_OBJECT_FROM16, Opcode.MOVE_OBJECT,
Opcode.INVOKE_VIRTUAL, // CharSequence atomic reference
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST, Opcode.CHECK_CAST,
Opcode.MOVE_OBJECT, // CharSequence reference, and control flow label. Insert code here. Opcode.MOVE_OBJECT,
null, // invoke-interface or invoke-virtual Opcode.INVOKE_INTERFACE, // Insert hook here
Opcode.MOVE_RESULT, Opcode.MOVE_RESULT,
Opcode.IF_EQZ, Opcode.IF_EQZ,
null, // invoke-interface or invoke-virtual Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT, Opcode.MOVE_RESULT_OBJECT,
Opcode.GOTO, Opcode.GOTO
) )
) )

View file

@ -83,10 +83,8 @@ class ReturnYouTubeDislikePatch : BytecodePatch(
// since the underlying (likes only) text did not change. // since the underlying (likes only) text did not change.
// This hook handles all situations, as it's where the created Spans are stored and later reused. // This hook handles all situations, as it's where the created Spans are stored and later reused.
TextComponentContextFingerprint.also { TextComponentContextFingerprint.also {
it.resolve( if (!it.resolve(context, TextComponentConstructorFingerprint.result!!.classDef))
context, throw it.exception
TextComponentConstructorFingerprint.result!!.classDef
)
}.result?.also { result -> }.result?.also { result ->
if (!TextComponentAtomicReferenceFingerprint.resolve(context, result.method, result.classDef)) if (!TextComponentAtomicReferenceFingerprint.resolve(context, result.method, result.classDef))
throw TextComponentAtomicReferenceFingerprint.exception throw TextComponentAtomicReferenceFingerprint.exception
@ -96,33 +94,46 @@ class ReturnYouTubeDislikePatch : BytecodePatch(
val atomicReferenceStartIndex = TextComponentAtomicReferenceFingerprint.result!! val atomicReferenceStartIndex = TextComponentAtomicReferenceFingerprint.result!!
.scanResult.patternScanResult!!.startIndex .scanResult.patternScanResult!!.startIndex
val insertIndex = atomicReferenceStartIndex + 7 val insertIndex = atomicReferenceStartIndex + 6
textComponentContextFingerprintResult.mutableMethod.apply { textComponentContextFingerprintResult.mutableMethod.apply {
// Get the conversion context obfuscated field name, and the registers for the AtomicReference and CharSequence // Get the conversion context obfuscated field name, and the registers for the AtomicReference and CharSequence
val conversionContextFieldReference = val conversionContextFieldReference =
getInstruction<ReferenceInstruction>(conversionContextIndex).reference getInstruction<ReferenceInstruction>(conversionContextIndex).reference
// any free register // Reuse the free register to make room for the atomic reference register.
val contextRegister = val freeRegister =
getInstruction<TwoRegisterInstruction>(atomicReferenceStartIndex).registerB getInstruction<TwoRegisterInstruction>(atomicReferenceStartIndex).registerB
val atomicReferenceRegister = val atomicReferenceRegister =
getInstruction<FiveRegisterInstruction>(atomicReferenceStartIndex + 4).registerC getInstruction<FiveRegisterInstruction>(atomicReferenceStartIndex + 1).registerC
val moveCharSequenceInstruction = getInstruction<TwoRegisterInstruction>(insertIndex) val moveCharSequenceInstruction = getInstruction<TwoRegisterInstruction>(insertIndex - 1)
val charSequenceRegister = moveCharSequenceInstruction.registerB val charSequenceSourceRegister = moveCharSequenceInstruction.registerB
val charSequenceTargetRegister = moveCharSequenceInstruction.registerA
// Insert as first instructions at the control flow label. // In order to preserve the atomic reference register, because it is overwritten,
// Must replace the existing instruction to preserve the label, and then insert the remaining instructions. // use another free register to store it.
replaceInstruction(insertIndex, "move-object/from16 v$contextRegister, p0") replaceInstruction(
atomicReferenceStartIndex + 2,
"move-result-object v$freeRegister"
)
replaceInstruction(
atomicReferenceStartIndex + 3,
"move-object v$charSequenceSourceRegister, v$freeRegister"
)
// Move the current instance to the free register, and get the conversion context from it.
replaceInstruction(insertIndex - 1, "move-object/from16 v$freeRegister, p0")
addInstructions( addInstructions(
insertIndex + 1, insertIndex,
""" """
iget-object v$contextRegister, v$contextRegister, $conversionContextFieldReference # copy obfuscated context field into free register # Move context to free register
invoke-static {v$contextRegister, v$atomicReferenceRegister, v$charSequenceRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/CharSequence;)Ljava/lang/CharSequence; iget-object v$freeRegister, v$freeRegister, $conversionContextFieldReference
move-result-object v$charSequenceRegister invoke-static {v$freeRegister, v$atomicReferenceRegister, v$charSequenceSourceRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->onLithoTextLoaded(Ljava/lang/Object;Ljava/util/concurrent/atomic/AtomicReference;Ljava/lang/CharSequence;)Ljava/lang/CharSequence;
move-object v${moveCharSequenceInstruction.registerA}, v${charSequenceRegister} # original instruction at the insertion point move-result-object v$freeRegister
# Replace the original char sequence with the modified one.
move-object v${charSequenceTargetRegister}, v${freeRegister}
""" """
) )
} }

View file

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.misc.links.open.annotations package app.revanced.patches.youtube.misc.links.annotations
import app.revanced.patcher.annotation.Compatibility import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package import app.revanced.patcher.annotation.Package

View file

@ -1,17 +0,0 @@
package app.revanced.patches.youtube.misc.links.open.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
object BindSessionServiceFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf(
Opcode.IPUT_OBJECT,
Opcode.NEW_INSTANCE,
Opcode.CONST_STRING
),
strings = listOf("android.support.customtabs.action.CustomTabsService")
)

View file

@ -1,18 +0,0 @@
package app.revanced.patches.youtube.misc.links.open.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
object GetCustomTabPackageNameFingerprint : MethodFingerprint(
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.CONST_STRING
),
strings = listOf("android.support.customtabs.action.CustomTabsService")
)

View file

@ -1,18 +0,0 @@
package app.revanced.patches.youtube.misc.links.open.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
object InitializeCustomTabSupportFingerprint : MethodFingerprint(
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
Opcode.CONST_STRING
),
strings = listOf("android.support.customtabs.action.CustomTabsService")
)

View file

@ -1,59 +0,0 @@
package app.revanced.patches.youtube.misc.links.open.patch
import app.revanced.extensions.exception
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patches.shared.settings.preference.impl.StringResource
import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
import app.revanced.patches.youtube.misc.links.open.annotations.OpenLinksExternallyCompatibility
import app.revanced.patches.youtube.misc.links.open.fingerprints.BindSessionServiceFingerprint
import app.revanced.patches.youtube.misc.links.open.fingerprints.GetCustomTabPackageNameFingerprint
import app.revanced.patches.youtube.misc.links.open.fingerprints.InitializeCustomTabSupportFingerprint
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction21c
@Patch
@Name("Open links externally")
@Description("Open links outside of the app directly in your browser.")
@OpenLinksExternallyCompatibility
class OpenLinksExternallyPatch : BytecodePatch(
listOf(
GetCustomTabPackageNameFingerprint,
BindSessionServiceFingerprint,
InitializeCustomTabSupportFingerprint
)
) {
override fun execute(context: BytecodeContext) {
SettingsPatch.PreferenceScreen.MISC.addPreferences(
SwitchPreference(
"revanced_external_browser",
StringResource("revanced_external_browser_title", "Open links in browser"),
StringResource("revanced_external_browser_summary_on", "Opening links externally"),
StringResource("revanced_external_browser_summary_off", "Opening links in app")
)
)
arrayOf(
GetCustomTabPackageNameFingerprint,
BindSessionServiceFingerprint,
InitializeCustomTabSupportFingerprint
).forEach {
val result = it.result ?: throw it.exception
val insertIndex = result.scanResult.patternScanResult!!.endIndex + 1
with(result.mutableMethod) {
val register = (implementation!!.instructions[insertIndex - 1] as Instruction21c).registerA
addInstructions(
insertIndex,
"""
invoke-static {v$register}, Lapp/revanced/integrations/patches/OpenLinksExternallyPatch;->enableExternalBrowser(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$register
"""
)
}
}
}
}

View file

@ -0,0 +1,63 @@
package app.revanced.patches.youtube.misc.links.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.shared.settings.preference.impl.StringResource
import app.revanced.patches.shared.settings.preference.impl.SwitchPreference
import app.revanced.patches.youtube.misc.links.annotations.OpenLinksExternallyCompatibility
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.util.patch.AbstractTransformInstructionsPatch
import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.StringReference
@Patch
@Name("Open links externally")
@Description("Open links outside of the app directly in your browser.")
@OpenLinksExternallyCompatibility
class OpenLinksExternallyPatch : AbstractTransformInstructionsPatch<Pair<Int, Int>>(
) {
override fun filterMap(
classDef: ClassDef, method: Method, instruction: Instruction, instructionIndex: Int
): Pair<Int, Int>? {
if (instruction !is ReferenceInstruction) return null
val reference = instruction.reference as? StringReference ?: return null
if (reference.string != "android.support.customtabs.action.CustomTabsService") return null
return instructionIndex to (instruction as OneRegisterInstruction).registerA
}
override fun transform(mutableMethod: MutableMethod, entry: Pair<Int, Int>) {
val (intentStringIndex, register) = entry
// Hook the intent string.
mutableMethod.addInstructions(
intentStringIndex + 1,
"""
invoke-static {v$register}, Lapp/revanced/integrations/patches/OpenLinksExternallyPatch;->getIntent(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$register
"""
)
}
override fun execute(context: BytecodeContext) {
SettingsPatch.PreferenceScreen.MISC.addPreferences(
SwitchPreference(
"revanced_external_browser",
StringResource("revanced_external_browser_title", "Open links in browser"),
StringResource("revanced_external_browser_summary_on", "Opening links externally"),
StringResource("revanced_external_browser_summary_off", "Opening links in app")
)
)
super.execute(context)
}
}

View file

@ -8,7 +8,7 @@ import com.android.tools.smali.dexlib2.iface.ClassDef
import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.Instruction
internal abstract class AbstractTransformInstructionsPatch<T> : BytecodePatch() { abstract class AbstractTransformInstructionsPatch<T> : BytecodePatch() {
abstract fun filterMap( abstract fun filterMap(
classDef: ClassDef, classDef: ClassDef,

View file

@ -8,9 +8,9 @@ import com.android.tools.smali.dexlib2.iface.instruction.Instruction
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal typealias Instruction35cInfo = Triple<IMethodCall, Instruction35c, Int> typealias Instruction35cInfo = Triple<IMethodCall, Instruction35c, Int>
internal interface IMethodCall { interface IMethodCall {
val definedClassName: String val definedClassName: String
val methodName: String val methodName: String
val methodParams: Array<String> val methodParams: Array<String>
@ -62,14 +62,14 @@ internal interface IMethodCall {
} }
} }
internal inline fun <reified E> fromMethodReference(methodReference: MethodReference) inline fun <reified E> fromMethodReference(methodReference: MethodReference)
where E : Enum<E>, E : IMethodCall = enumValues<E>().firstOrNull { search -> where E : Enum<E>, E : IMethodCall = enumValues<E>().firstOrNull { search ->
search.definedClassName == methodReference.definingClass search.definedClassName == methodReference.definingClass
&& search.methodName == methodReference.name && search.methodName == methodReference.name
&& methodReference.parameterTypes.toTypedArray().contentEquals(search.methodParams) && methodReference.parameterTypes.toTypedArray().contentEquals(search.methodParams)
} }
internal inline fun <reified E> filterMapInstruction35c( inline fun <reified E> filterMapInstruction35c(
integrationsClassDescriptorPrefix: String, integrationsClassDescriptorPrefix: String,
classDef: ClassDef, classDef: ClassDef,
instruction: Instruction, instruction: Instruction,