feat(YouTube): Support version 18.32.39
				
					
				
			This commit is contained in:
		
							parent
							
								
									5afb63e052
								
							
						
					
					
						commit
						7b503e2336
					
				| 
						 | 
					@ -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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 =
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -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}
 | 
				
			||||||
                    """
 | 
					                    """
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					@ -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")
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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")
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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")
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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
 | 
					 | 
				
			||||||
                    """
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -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)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue