refactor(youtube/video-information): include video speed (#1843)

Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
This commit is contained in:
LisoUseInAIKyrios 2023-04-04 11:00:13 +04:00 committed by oSumAtrIX
parent 7403fc86ae
commit 33f795350d
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
7 changed files with 93 additions and 96 deletions

View File

@ -4,19 +4,7 @@ import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package import app.revanced.patcher.annotation.Package
@Compatibility( @Compatibility(
[Package( [Package("com.google.android.youtube", arrayOf("18.08.37"))]
"com.google.android.youtube", arrayOf(
"17.49.37",
"18.03.36",
"18.03.42",
"18.04.35",
"18.04.41",
"18.05.32",
"18.05.35",
"18.05.40",
"18.08.37"
)
)]
) )
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
internal annotation class SponsorBlockCompatibility internal annotation class SponsorBlockCompatibility

View File

@ -29,7 +29,6 @@ import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch
import app.revanced.patches.youtube.misc.playertype.patch.PlayerTypeHookPatch import app.revanced.patches.youtube.misc.playertype.patch.PlayerTypeHookPatch
import app.revanced.patches.youtube.misc.video.information.patch.VideoInformationPatch import app.revanced.patches.youtube.misc.video.information.patch.VideoInformationPatch
import app.revanced.patches.youtube.misc.video.speed.remember.patch.RememberPlaybackSpeedPatch
import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch
import org.jf.dexlib2.Opcode import org.jf.dexlib2.Opcode
import org.jf.dexlib2.iface.instruction.* import org.jf.dexlib2.iface.instruction.*
@ -41,11 +40,13 @@ import org.jf.dexlib2.iface.reference.StringReference
@Patch @Patch
@DependsOn( @DependsOn(
dependencies = [ dependencies = [
VideoInformationPatch::class, // updates video information and adds method to seek in video
VideoIdPatch::class,
PlayerControlsBytecodePatch::class,
PlayerTypeHookPatch::class,
IntegrationsPatch::class, IntegrationsPatch::class,
VideoIdPatch::class,
// Required to skip segments on time.
VideoInformationPatch::class,
// Used to prevent SponsorBlock from running on Shorts because SponsorBlock does not yet support Shorts.
PlayerTypeHookPatch::class,
PlayerControlsBytecodePatch::class,
SponsorBlockResourcePatch::class, SponsorBlockResourcePatch::class,
] ]
) )
@ -75,7 +76,7 @@ class SponsorBlockBytecodePatch : BytecodePatch(
override fun execute(context: BytecodeContext): PatchResult { override fun execute(context: BytecodeContext): PatchResult {
/* /*
Hook the video time methods * Hook the video time methods
*/ */
with(VideoInformationPatch) { with(VideoInformationPatch) {
videoTimeHook( videoTimeHook(
@ -85,12 +86,12 @@ class SponsorBlockBytecodePatch : BytecodePatch(
} }
/* /*
Set current video id * Set current video id
*/ */
VideoIdPatch.injectCallBackgroundPlay("$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setCurrentVideoId(Ljava/lang/String;)V") VideoIdPatch.injectCallBackgroundPlay("$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setCurrentVideoId(Ljava/lang/String;)V")
/* /*
Seekbar drawing * Seekbar drawing
*/ */
val seekbarSignatureResult = SeekbarFingerprint.result!!.let { val seekbarSignatureResult = SeekbarFingerprint.result!!.let {
SeekbarOnDrawFingerprint.apply { resolve(context, it.mutableClass) } SeekbarOnDrawFingerprint.apply { resolve(context, it.mutableClass) }
@ -99,7 +100,7 @@ class SponsorBlockBytecodePatch : BytecodePatch(
val seekbarMethodInstructions = seekbarMethod.implementation!!.instructions val seekbarMethodInstructions = seekbarMethod.implementation!!.instructions
/* /*
Get the instance of the seekbar rectangle * Get the instance of the seekbar rectangle
*/ */
for ((index, instruction) in seekbarMethodInstructions.withIndex()) { for ((index, instruction) in seekbarMethodInstructions.withIndex()) {
if (instruction.opcode != Opcode.MOVE_OBJECT_FROM16) continue if (instruction.opcode != Opcode.MOVE_OBJECT_FROM16) continue
@ -127,8 +128,8 @@ class SponsorBlockBytecodePatch : BytecodePatch(
} }
/* /*
Set rectangle absolute left and right positions * Set rectangle absolute left and right positions
*/ */
val drawRectangleInstructions = seekbarMethodInstructions.withIndex().filter { (_, instruction) -> val drawRectangleInstructions = seekbarMethodInstructions.withIndex().filter { (_, instruction) ->
instruction is ReferenceInstruction && (instruction.reference as? MethodReference)?.name == "drawRect" instruction is ReferenceInstruction && (instruction.reference as? MethodReference)?.name == "drawRect"
}.map { (index, instruction) -> // TODO: improve code }.map { (index, instruction) -> // TODO: improve code
@ -150,8 +151,8 @@ class SponsorBlockBytecodePatch : BytecodePatch(
) )
/* /*
Draw segment * Draw segment
*/ */
val drawSegmentInstructionInsertIndex = (seekbarMethodInstructions.size - 1 - 2) val drawSegmentInstructionInsertIndex = (seekbarMethodInstructions.size - 1 - 2)
val (canvasInstance, centerY) = (seekbarMethodInstructions[drawSegmentInstructionInsertIndex] as FiveRegisterInstruction).let { val (canvasInstance, centerY) = (seekbarMethodInstructions[drawSegmentInstructionInsertIndex] as FiveRegisterInstruction).let {
it.registerC to it.registerE it.registerC to it.registerE
@ -162,7 +163,7 @@ class SponsorBlockBytecodePatch : BytecodePatch(
) )
/* /*
Voting & Shield button * Voting & Shield button
*/ */
val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult
@ -268,7 +269,9 @@ class SponsorBlockBytecodePatch : BytecodePatch(
} ?: return PatchResultError("Could not find the method which contains the replaceMeWith* strings") } ?: return PatchResultError("Could not find the method which contains the replaceMeWith* strings")
// detect end of the video has been reached // The vote and create segment buttons automatically change their visibility when appropriate,
// but if buttons are showing when the end of the video is reached then they will not automatically hide.
// Add a hook to forcefully hide when the end of the video is reached.
AutoRepeatParentFingerprint.result ?: return AutoRepeatParentFingerprint.toErrorResult() AutoRepeatParentFingerprint.result ?: return AutoRepeatParentFingerprint.toErrorResult()
AutoRepeatFingerprint.also { AutoRepeatFingerprint.also {
it.resolve(context, AutoRepeatParentFingerprint.result!!.classDef) it.resolve(context, AutoRepeatParentFingerprint.result!!.classDef)

View File

@ -4,19 +4,7 @@ import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package import app.revanced.patcher.annotation.Package
@Compatibility( @Compatibility(
[Package( [Package("com.google.android.youtube", arrayOf("18.08.37"))]
"com.google.android.youtube", arrayOf(
"17.49.37",
"18.03.36",
"18.03.42",
"18.04.35",
"18.04.41",
"18.05.32",
"18.05.35",
"18.05.40",
"18.08.37"
)
)]
) )
@Target(AnnotationTarget.CLASS) @Target(AnnotationTarget.CLASS)
internal annotation class VideoInformationCompatibility internal annotation class VideoInformationCompatibility

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.misc.video.speed.remember.fingerprint package app.revanced.patches.youtube.misc.video.information.fingerprints
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.Opcode import org.jf.dexlib2.Opcode

View File

@ -1,5 +1,6 @@
package app.revanced.patches.youtube.misc.video.information.patch package app.revanced.patches.youtube.misc.video.information.patch
import app.revanced.extensions.toErrorResult
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.annotation.Version import app.revanced.patcher.annotation.Version
@ -20,10 +21,16 @@ import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMu
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.video.information.annotation.VideoInformationCompatibility import app.revanced.patches.youtube.misc.video.information.annotation.VideoInformationCompatibility
import app.revanced.patches.youtube.misc.video.information.fingerprints.* import app.revanced.patches.youtube.misc.video.information.fingerprints.*
import app.revanced.patches.youtube.misc.video.speed.remember.patch.RememberPlaybackSpeedPatch
import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch
import org.jf.dexlib2.AccessFlags import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.BuilderInstruction
import org.jf.dexlib2.builder.MutableMethodImplementation import org.jf.dexlib2.builder.MutableMethodImplementation
import org.jf.dexlib2.iface.instruction.FiveRegisterInstruction
import org.jf.dexlib2.iface.instruction.Instruction
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.immutable.ImmutableMethod import org.jf.dexlib2.immutable.ImmutableMethod
import org.jf.dexlib2.immutable.ImmutableMethodParameter import org.jf.dexlib2.immutable.ImmutableMethodParameter
import org.jf.dexlib2.util.MethodUtil import org.jf.dexlib2.util.MethodUtil
@ -39,6 +46,7 @@ class VideoInformationPatch : BytecodePatch(
CreateVideoPlayerSeekbarFingerprint, CreateVideoPlayerSeekbarFingerprint,
PlayerControllerSetTimeReferenceFingerprint, PlayerControllerSetTimeReferenceFingerprint,
VideoTimeFingerprint, VideoTimeFingerprint,
OnPlaybackSpeedItemClickFingerprint,
) )
) { ) {
override fun execute(context: BytecodeContext): PatchResult { override fun execute(context: BytecodeContext): PatchResult {
@ -96,15 +104,15 @@ class VideoInformationPatch : BytecodePatch(
} }
/* /*
Inject call for video id * Inject call for video id
*/ */
val videoIdMethodDescriptor = "$INTEGRATIONS_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V" val videoIdMethodDescriptor = "$INTEGRATIONS_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V"
VideoIdPatch.injectCall(videoIdMethodDescriptor) VideoIdPatch.injectCall(videoIdMethodDescriptor)
VideoIdPatch.injectCallBackgroundPlay(videoIdMethodDescriptor) VideoIdPatch.injectCallBackgroundPlay(videoIdMethodDescriptor)
/* /*
Set the video time method * Set the video time method
*/ */
with(PlayerControllerSetTimeReferenceFingerprint.result!!) { with(PlayerControllerSetTimeReferenceFingerprint.result!!) {
timeMethod = context.toMethodWalker(method) timeMethod = context.toMethodWalker(method)
.nextMethod(scanResult.patternScanResult!!.startIndex, true) .nextMethod(scanResult.patternScanResult!!.startIndex, true)
@ -112,7 +120,7 @@ class VideoInformationPatch : BytecodePatch(
} }
/* /*
Set the high precision video time method * Set the high precision video time method
*/ */
highPrecisionTimeMethod = highPrecisionTimeMethod =
(object : MethodFingerprint("V", null, listOf("J", "J", "J", "J", "I", "L"), null) {}).also { (object : MethodFingerprint("V", null, listOf("J", "J", "J", "J", "I", "L"), null) {}).also {
@ -120,10 +128,31 @@ class VideoInformationPatch : BytecodePatch(
}.result!!.mutableMethod }.result!!.mutableMethod
/* /*
Hook the methods which set the time * Hook the methods which set the time
*/ */
highPrecisionTimeHook(INTEGRATIONS_CLASS_DESCRIPTOR, "setVideoTimeHighPrecision") highPrecisionTimeHook(INTEGRATIONS_CLASS_DESCRIPTOR, "setVideoTimeHighPrecision")
/*
* Hook the user playback speed selection
*/
OnPlaybackSpeedItemClickFingerprint.result?.apply {
speedSelectionInsertMethod = mutableMethod
speedSelectionInsertIndex = scanResult.patternScanResult!!.startIndex - 3
speedSelectionValueRegister =
(mutableMethod.instruction(speedSelectionInsertIndex) as FiveRegisterInstruction).registerD
val speedSelectionMethodInstructions = mutableMethod.implementation!!.instructions
setPlaybackSpeedContainerClassFieldReference =
getReference(speedSelectionMethodInstructions, -1, Opcode.IF_EQZ)
setPlaybackSpeedClassFieldReference =
getReference(speedSelectionMethodInstructions, 1, Opcode.IGET)
setPlaybackSpeedMethodReference =
getReference(speedSelectionMethodInstructions, 2, Opcode.IGET)
} ?: return OnPlaybackSpeedItemClickFingerprint.toErrorResult()
userSelectedPlaybackSpeedHook(INTEGRATIONS_CLASS_DESCRIPTOR, "userSelectedPlaybackSpeed")
return PatchResultSuccess() return PatchResultSuccess()
} }
@ -185,5 +214,30 @@ class VideoInformationPatch : BytecodePatch(
TIME(2), TIME(2),
HIGH_PRECISION_TIME(0), HIGH_PRECISION_TIME(0),
} }
private fun getReference(instructions: List<BuilderInstruction>, offset: Int, opcode: Opcode) =
instructions[instructions.indexOfFirst { it.opcode == opcode } + offset].reference
val Instruction.reference get() = (this as ReferenceInstruction).reference.toString()
private lateinit var speedSelectionInsertMethod: MutableMethod
private var speedSelectionInsertIndex = 0
private var speedSelectionValueRegister = 0
/**
* Hook the video speed selected by the user.
*/
internal fun userSelectedPlaybackSpeedHook(targetMethodClass: String, targetMethodName: String) =
speedSelectionInsertMethod.addInstruction(
speedSelectionInsertIndex++,
"invoke-static {v$speedSelectionValueRegister}, $targetMethodClass->$targetMethodName(F)V"
)
/**
* Used by [RememberPlaybackSpeedPatch]
*/
internal lateinit var setPlaybackSpeedContainerClassFieldReference: String
internal lateinit var setPlaybackSpeedClassFieldReference: String
internal lateinit var setPlaybackSpeedMethodReference: String
} }
} }

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.misc.video.speed.remember.fingerprint package app.revanced.patches.youtube.misc.video.speed.current.fingerprint
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint

View File

@ -5,7 +5,6 @@ import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.addInstruction
import app.revanced.patcher.extensions.addInstructions import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.instruction import app.revanced.patcher.extensions.instruction
import app.revanced.patcher.patch.* import app.revanced.patcher.patch.*
@ -16,24 +15,20 @@ 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.youtube.misc.integrations.patch.IntegrationsPatch import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
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.video.information.patch.VideoInformationPatch
import app.revanced.patches.youtube.misc.video.information.patch.VideoInformationPatch.Companion.reference
import app.revanced.patches.youtube.misc.video.speed.current.fingerprint.InitializePlaybackSpeedValuesFingerprint
import app.revanced.patches.youtube.misc.video.speed.remember.annotation.RememberPlaybackSpeedCompatibility import app.revanced.patches.youtube.misc.video.speed.remember.annotation.RememberPlaybackSpeedCompatibility
import app.revanced.patches.youtube.misc.video.speed.remember.fingerprint.InitializePlaybackSpeedValuesFingerprint
import app.revanced.patches.youtube.misc.video.speed.remember.fingerprint.OnPlaybackSpeedItemClickFingerprint
import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch import app.revanced.patches.youtube.misc.video.videoid.patch.VideoIdPatch
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.iface.instruction.FiveRegisterInstruction
import org.jf.dexlib2.iface.instruction.Instruction
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
@Patch @Patch
@Name("remember-playback-speed") @Name("remember-playback-speed")
@Description("Adds the ability to remember the playback speed you chose in the video playback speed flyout.") @Description("Adds the ability to remember the playback speed you chose in the video playback speed flyout.")
@DependsOn([IntegrationsPatch::class, SettingsPatch::class, VideoIdPatch::class]) @DependsOn([IntegrationsPatch::class, SettingsPatch::class, VideoIdPatch::class, VideoInformationPatch::class])
@RememberPlaybackSpeedCompatibility @RememberPlaybackSpeedCompatibility
@Version("0.0.1") @Version("0.0.1")
class RememberPlaybackSpeedPatch : BytecodePatch( class RememberPlaybackSpeedPatch : BytecodePatch(
listOf( listOf(
OnPlaybackSpeedItemClickFingerprint,
InitializePlaybackSpeedValuesFingerprint InitializePlaybackSpeedValuesFingerprint
) )
) { ) {
@ -59,38 +54,21 @@ class RememberPlaybackSpeedPatch : BytecodePatch(
VideoIdPatch.injectCall("${INTEGRATIONS_CLASS_DESCRIPTOR}->newVideoLoaded(Ljava/lang/String;)V") VideoIdPatch.injectCall("${INTEGRATIONS_CLASS_DESCRIPTOR}->newVideoLoaded(Ljava/lang/String;)V")
VideoInformationPatch.userSelectedPlaybackSpeedHook(
INTEGRATIONS_CLASS_DESCRIPTOR, "userSelectedPlaybackSpeed")
/* /*
* The following code works by hooking the method which is called when the user selects a playback speed * Hook the code that is called when the playback speeds are initialized, and sets the playback speed
* to remember the last selected playback speed.
*
* It also hooks the method which is called when the playback speeds are initialized.
* Conveniently, at this point the playback speed is set to the remembered playback speed.
*/ */
// Set the remembered playback speed.
InitializePlaybackSpeedValuesFingerprint.result?.apply { InitializePlaybackSpeedValuesFingerprint.result?.apply {
// Infer everything necessary for calling the method setPlaybackSpeed(). // Infer everything necessary for calling the method setPlaybackSpeed().
val instructions = OnPlaybackSpeedItemClickFingerprint.result!!.mutableMethod.implementation!!.instructions
fun getReference(offset: Int = 0, opcode: Opcode) =
instructions[instructions.indexOfFirst { it.opcode == opcode } + offset].reference
val setPlaybackSpeedContainerClassFieldReference =
getReference(-1, Opcode.IF_EQZ)
val setPlaybackSpeedClassFieldReference =
getReference(1, Opcode.IGET)
val setPlaybackSpeedMethodReference =
getReference(2, Opcode.IGET)
val onItemClickListenerClassFieldReference = mutableMethod.instruction(0).reference val onItemClickListenerClassFieldReference = mutableMethod.instruction(0).reference
// Registers are not used at index 0, so they can be freely used. // Registers are not used at index 0, so they can be freely used.
mutableMethod.addInstructions( mutableMethod.addInstructions(
0, 0,
""" """
invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->getCurrentPlaybackSpeed()F invoke-static { }, $INTEGRATIONS_CLASS_DESCRIPTOR->getPlaybackSpeedOverride()F
move-result v0 move-result v0
# Check if the playback speed is not 1.0x. # Check if the playback speed is not 1.0x.
@ -102,30 +80,18 @@ class RememberPlaybackSpeedPatch : BytecodePatch(
iget-object v1, p0, $onItemClickListenerClassFieldReference iget-object v1, p0, $onItemClickListenerClassFieldReference
# Get the container class field. # Get the container class field.
iget-object v1, v1, $setPlaybackSpeedContainerClassFieldReference iget-object v1, v1, ${VideoInformationPatch.setPlaybackSpeedContainerClassFieldReference}
# Get the field from its class. # Get the field from its class.
iget-object v2, v1, $setPlaybackSpeedClassFieldReference iget-object v2, v1, ${VideoInformationPatch.setPlaybackSpeedClassFieldReference}
# Invoke setPlaybackSpeed on that class. # Invoke setPlaybackSpeed on that class.
invoke-virtual {v2, v0}, $setPlaybackSpeedMethodReference invoke-virtual {v2, v0}, ${VideoInformationPatch.setPlaybackSpeedMethodReference}
""".trimIndent(), """.trimIndent(),
listOf(ExternalLabel("do_not_override", mutableMethod.instruction(0))) listOf(ExternalLabel("do_not_override", mutableMethod.instruction(0)))
) )
} ?: return InitializePlaybackSpeedValuesFingerprint.toErrorResult() } ?: return InitializePlaybackSpeedValuesFingerprint.toErrorResult()
// Remember the selected playback speed.
OnPlaybackSpeedItemClickFingerprint.result?.apply {
val setPlaybackSpeedIndex = scanResult.patternScanResult!!.startIndex - 3
val selectedPlaybackSpeedRegister =
(mutableMethod.instruction(setPlaybackSpeedIndex) as FiveRegisterInstruction).registerD
mutableMethod.addInstruction(
setPlaybackSpeedIndex,
"invoke-static { v$selectedPlaybackSpeedRegister }, $INTEGRATIONS_CLASS_DESCRIPTOR->setPlaybackSpeed(F)V"
)
} ?: return OnPlaybackSpeedItemClickFingerprint.toErrorResult()
return PatchResultSuccess() return PatchResultSuccess()
} }
@ -133,7 +99,5 @@ class RememberPlaybackSpeedPatch : BytecodePatch(
private companion object { private companion object {
const val INTEGRATIONS_CLASS_DESCRIPTOR = const val INTEGRATIONS_CLASS_DESCRIPTOR =
"Lapp/revanced/integrations/patches/playback/speed/RememberPlaybackSpeedPatch;" "Lapp/revanced/integrations/patches/playback/speed/RememberPlaybackSpeedPatch;"
val Instruction.reference get() = (this as ReferenceInstruction).reference.toString()
} }
} }