feat(YouTube - Playback Speed): Use modern custom speed dialog (#5069)
Co-authored-by: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com>
This commit is contained in:
@ -123,3 +123,13 @@ internal val playbackSpeedMenuSpeedChangedFingerprint = fingerprint {
|
||||
Opcode.RETURN_OBJECT,
|
||||
)
|
||||
}
|
||||
|
||||
internal val playbackSpeedClassFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("L")
|
||||
parameters("L")
|
||||
opcodes(
|
||||
Opcode.RETURN_OBJECT
|
||||
)
|
||||
strings("PLAYBACK_RATE_MENU_BOTTOM_SHEET_FRAGMENT")
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
||||
import app.revanced.patcher.util.smali.toInstructions
|
||||
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint
|
||||
import app.revanced.patches.youtube.video.playerresponse.Hook
|
||||
@ -16,6 +17,8 @@ import app.revanced.patches.youtube.video.videoid.hookBackgroundPlayVideoId
|
||||
import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId
|
||||
import app.revanced.patches.youtube.video.videoid.hookVideoId
|
||||
import app.revanced.patches.youtube.video.videoid.videoIdPatch
|
||||
import app.revanced.util.addInstructionsAtControlFlowLabel
|
||||
import app.revanced.util.addStaticFieldToExtension
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
@ -29,6 +32,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
|
||||
@ -189,6 +193,72 @@ val videoInformationPatch = bytecodePatch(
|
||||
proxy(classes.first { it.type == setPlaybackSpeedMethodReference.definingClass })
|
||||
.mutableClass.methods.first { it.name == setPlaybackSpeedMethodReference.name }
|
||||
setPlaybackSpeedMethodIndex = 0
|
||||
|
||||
// Add override playback speed method.
|
||||
onPlaybackSpeedItemClickFingerprint.classDef.methods.add(
|
||||
ImmutableMethod(
|
||||
definingClass,
|
||||
"overridePlaybackSpeed",
|
||||
listOf(ImmutableMethodParameter("F", annotations, null)),
|
||||
"V",
|
||||
AccessFlags.PUBLIC.value or AccessFlags.PUBLIC.value,
|
||||
annotations,
|
||||
null,
|
||||
ImmutableMethodImplementation(
|
||||
4,
|
||||
"""
|
||||
# Check if the playback speed is not auto (-2.0f)
|
||||
const/4 v0, 0x0
|
||||
cmpg-float v0, v3, v0
|
||||
if-lez v0, :ignore
|
||||
|
||||
# Get the container class field.
|
||||
iget-object v0, v2, $setPlaybackSpeedContainerClassFieldReference
|
||||
|
||||
# For some reason, in YouTube 19.44.39 this value is sometimes null.
|
||||
if-eqz v0, :ignore
|
||||
|
||||
# Get the field from its class.
|
||||
iget-object v1, v0, $setPlaybackSpeedClassFieldReference
|
||||
|
||||
# Invoke setPlaybackSpeed on that class.
|
||||
invoke-virtual {v1, v3}, $setPlaybackSpeedMethodReference
|
||||
|
||||
:ignore
|
||||
return-void
|
||||
""".toInstructions(), null, null
|
||||
)
|
||||
).toMutable()
|
||||
)
|
||||
}
|
||||
|
||||
playbackSpeedClassFingerprint.method.apply {
|
||||
val index = indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
|
||||
val register = getInstruction<OneRegisterInstruction>(index).registerA
|
||||
val playbackSpeedClass = this.returnType
|
||||
|
||||
// Set playback speed class.
|
||||
addInstructionsAtControlFlowLabel(
|
||||
index,
|
||||
"sput-object v$register, $EXTENSION_CLASS_DESCRIPTOR->playbackSpeedClass:$playbackSpeedClass"
|
||||
)
|
||||
|
||||
val smaliInstructions =
|
||||
"""
|
||||
if-eqz v0, :ignore
|
||||
invoke-virtual {v0, p0}, $playbackSpeedClass->overridePlaybackSpeed(F)V
|
||||
return-void
|
||||
:ignore
|
||||
nop
|
||||
"""
|
||||
|
||||
addStaticFieldToExtension(
|
||||
EXTENSION_CLASS_DESCRIPTOR,
|
||||
"overridePlaybackSpeed",
|
||||
"playbackSpeedClass",
|
||||
playbackSpeedClass,
|
||||
smaliInstructions
|
||||
)
|
||||
}
|
||||
|
||||
// Handle new playback speed menu.
|
||||
|
@ -1,19 +1,11 @@
|
||||
package app.revanced.patches.youtube.video.speed.custom
|
||||
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.patch.resourcePatch
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patches.all.misc.resources.addResources
|
||||
import app.revanced.patches.all.misc.resources.addResourcesPatch
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
|
||||
import app.revanced.patches.shared.misc.mapping.resourceMappings
|
||||
import app.revanced.patches.shared.misc.settings.preference.InputType
|
||||
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
|
||||
import app.revanced.patches.shared.misc.settings.preference.TextPreference
|
||||
@ -27,27 +19,11 @@ import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTr
|
||||
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch
|
||||
import app.revanced.patches.youtube.misc.settings.settingsPatch
|
||||
import app.revanced.patches.youtube.video.speed.settingsMenuVideoSpeedGroup
|
||||
import app.revanced.util.*
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import app.revanced.util.indexOfFirstInstructionOrThrow
|
||||
import app.revanced.util.indexOfFirstLiteralInstruction
|
||||
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.NarrowLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
|
||||
internal var speedUnavailableId = -1L
|
||||
private set
|
||||
|
||||
private val customPlaybackSpeedResourcePatch = resourcePatch {
|
||||
dependsOn(resourceMappingPatch)
|
||||
|
||||
execute {
|
||||
speedUnavailableId = resourceMappings[
|
||||
"string",
|
||||
"varispeed_unavailable_message",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
private const val FILTER_CLASS_DESCRIPTOR =
|
||||
"Lapp/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilterPatch;"
|
||||
@ -64,8 +40,7 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||
addResourcesPatch,
|
||||
lithoFilterPatch,
|
||||
versionCheckPatch,
|
||||
recyclerViewTreeHookPatch,
|
||||
customPlaybackSpeedResourcePatch
|
||||
recyclerViewTreeHookPatch
|
||||
)
|
||||
|
||||
execute {
|
||||
@ -87,38 +62,6 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||
)
|
||||
}
|
||||
|
||||
// Replace the speeds float array with custom speeds.
|
||||
speedArrayGeneratorFingerprint.method.apply {
|
||||
val sizeCallIndex = indexOfFirstInstructionOrThrow { getReference<MethodReference>()?.name == "size" }
|
||||
val sizeCallResultRegister = getInstruction<OneRegisterInstruction>(sizeCallIndex + 1).registerA
|
||||
|
||||
replaceInstruction(sizeCallIndex + 1, "const/4 v$sizeCallResultRegister, 0x0")
|
||||
|
||||
val arrayLengthConstIndex = indexOfFirstLiteralInstructionOrThrow(7)
|
||||
val arrayLengthConstDestination = getInstruction<OneRegisterInstruction>(arrayLengthConstIndex).registerA
|
||||
val playbackSpeedsArrayType = "$EXTENSION_CLASS_DESCRIPTOR->customPlaybackSpeeds:[F"
|
||||
|
||||
addInstructions(
|
||||
arrayLengthConstIndex + 1,
|
||||
"""
|
||||
sget-object v$arrayLengthConstDestination, $playbackSpeedsArrayType
|
||||
array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
|
||||
""",
|
||||
)
|
||||
|
||||
val originalArrayFetchIndex = indexOfFirstInstructionOrThrow {
|
||||
val reference = getReference<FieldReference>()
|
||||
reference?.type == "[F" && reference.definingClass.endsWith("/PlayerConfigModel;")
|
||||
}
|
||||
val originalArrayFetchDestination =
|
||||
getInstruction<OneRegisterInstruction>(originalArrayFetchIndex).registerA
|
||||
|
||||
replaceInstruction(
|
||||
originalArrayFetchIndex,
|
||||
"sget-object v$originalArrayFetchDestination, $playbackSpeedsArrayType",
|
||||
)
|
||||
}
|
||||
|
||||
// Override the min/max speeds that can be used.
|
||||
speedLimiterFingerprint.method.apply {
|
||||
val limitMinIndex = indexOfFirstLiteralInstructionOrThrow(0.25f)
|
||||
@ -135,47 +78,7 @@ internal val customPlaybackSpeedPatch = bytecodePatch(
|
||||
replaceInstruction(limitMaxIndex, "const/high16 v$limitMaxRegister, 8.0f")
|
||||
}
|
||||
|
||||
// Add a static INSTANCE field to the class.
|
||||
// This is later used to call "showOldPlaybackSpeedMenu" on the instance.
|
||||
|
||||
val instanceField = ImmutableField(
|
||||
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
|
||||
"INSTANCE",
|
||||
getOldPlaybackSpeedsFingerprint.originalClassDef.type,
|
||||
AccessFlags.PUBLIC.value or AccessFlags.STATIC.value,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
).toMutable()
|
||||
|
||||
getOldPlaybackSpeedsFingerprint.classDef.staticFields.add(instanceField)
|
||||
// Set the INSTANCE field to the instance of the class.
|
||||
// In order to prevent a conflict with another patch, add the instruction at index 1.
|
||||
getOldPlaybackSpeedsFingerprint.method.addInstruction(1, "sput-object p0, $instanceField")
|
||||
|
||||
// Get the "showOldPlaybackSpeedMenu" method.
|
||||
// This is later called on the field INSTANCE.
|
||||
val showOldPlaybackSpeedMenuMethod = showOldPlaybackSpeedMenuFingerprint.match(
|
||||
getOldPlaybackSpeedsFingerprint.classDef,
|
||||
).method.toString()
|
||||
|
||||
// Insert the call to the "showOldPlaybackSpeedMenu" method on the field INSTANCE.
|
||||
showOldPlaybackSpeedMenuExtensionFingerprint.method.apply {
|
||||
addInstructionsWithLabels(
|
||||
instructions.lastIndex,
|
||||
"""
|
||||
sget-object v0, $instanceField
|
||||
if-nez v0, :not_null
|
||||
return-void
|
||||
:not_null
|
||||
invoke-virtual { v0 }, $showOldPlaybackSpeedMenuMethod
|
||||
""",
|
||||
)
|
||||
}
|
||||
|
||||
// region Force old video quality menu.
|
||||
// This is necessary, because there is no known way of adding custom playback speeds to the new menu.
|
||||
|
||||
// Close the unpatched playback dialog and show the modern custom dialog.
|
||||
addRecyclerViewTreeHook(EXTENSION_CLASS_DESCRIPTOR)
|
||||
|
||||
// Required to check if the playback speed menu is currently shown.
|
||||
|
@ -1,30 +1,9 @@
|
||||
package app.revanced.patches.youtube.video.speed.custom
|
||||
|
||||
import app.revanced.patcher.fingerprint
|
||||
import app.revanced.util.literal
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
|
||||
internal val getOldPlaybackSpeedsFingerprint = fingerprint {
|
||||
parameters("[L", "I")
|
||||
strings("menu_item_playback_speed")
|
||||
}
|
||||
|
||||
internal val showOldPlaybackSpeedMenuFingerprint = fingerprint {
|
||||
literal { speedUnavailableId }
|
||||
}
|
||||
|
||||
internal val showOldPlaybackSpeedMenuExtensionFingerprint = fingerprint {
|
||||
custom { method, _ -> method.name == "showOldPlaybackSpeedMenu" }
|
||||
}
|
||||
|
||||
internal val speedArrayGeneratorFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.STATIC)
|
||||
returns("[L")
|
||||
parameters("Lcom/google/android/libraries/youtube/innertube/model/player/PlayerResponseModel;")
|
||||
strings("0.0#")
|
||||
}
|
||||
|
||||
internal val speedLimiterFingerprint = fingerprint {
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
returns("V")
|
||||
|
@ -10,6 +10,7 @@ import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
|
||||
import app.revanced.patcher.patch.BytecodePatchContext
|
||||
import app.revanced.patcher.patch.PatchException
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
|
||||
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.shared.misc.mapping.get
|
||||
@ -31,6 +32,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.ThreeRegisterInstructio
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.WideLiteralInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.Reference
|
||||
import com.android.tools.smali.dexlib2.immutable.ImmutableField
|
||||
import com.android.tools.smali.dexlib2.util.MethodUtil
|
||||
import java.util.EnumSet
|
||||
|
||||
@ -962,6 +964,43 @@ private fun MutableMethod.overrideReturnValue(value: String, returnLate: Boolean
|
||||
}
|
||||
}
|
||||
|
||||
internal fun BytecodePatchContext.addStaticFieldToExtension(
|
||||
className: String,
|
||||
methodName: String,
|
||||
fieldName: String,
|
||||
objectClass: String,
|
||||
smaliInstructions: String
|
||||
) {
|
||||
val classDef = classes.find { classDef -> classDef.type == className }
|
||||
?: throw PatchException("No matching methods found in: $className")
|
||||
val mutableClass = proxy(classDef).mutableClass
|
||||
|
||||
val objectCall = "$mutableClass->$fieldName:$objectClass"
|
||||
|
||||
mutableClass.apply {
|
||||
methods.first { method -> method.name == methodName }.apply {
|
||||
staticFields.add(
|
||||
ImmutableField(
|
||||
definingClass,
|
||||
fieldName,
|
||||
objectClass,
|
||||
AccessFlags.PUBLIC.value or AccessFlags.STATIC.value,
|
||||
null,
|
||||
annotations,
|
||||
null
|
||||
).toMutable()
|
||||
)
|
||||
|
||||
addInstructionsWithLabels(
|
||||
0,
|
||||
"""
|
||||
sget-object v0, $objectCall
|
||||
""" + smaliInstructions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the custom condition for this fingerprint to check for a literal value.
|
||||
*
|
||||
|
@ -1466,7 +1466,7 @@ Enabling this can unlock higher video qualities"</string>
|
||||
</patch>
|
||||
<patch id="video.speed.button.playbackSpeedButtonPatch">
|
||||
<string name="revanced_playback_speed_dialog_button_title">Show speed dialog button</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Button is shown</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_on">Button is shown. Tap and hold to reset playback speed to default</string>
|
||||
<string name="revanced_playback_speed_dialog_button_summary_off">Button is not shown</string>
|
||||
</patch>
|
||||
<patch id="video.speed.custom.customPlaybackSpeedPatch">
|
||||
@ -1478,6 +1478,7 @@ Enabling this can unlock higher video qualities"</string>
|
||||
<string name="revanced_custom_playback_speeds_invalid">Custom speeds must be less than %s</string>
|
||||
<string name="revanced_custom_playback_speeds_parse_exception">Invalid custom playback speeds</string>
|
||||
<string name="revanced_custom_playback_speeds_auto">Auto</string>
|
||||
<string name="revanced_custom_playback_speeds_reset_toast">Playback speed reset to: %s</string>
|
||||
<string name="revanced_speed_tap_and_hold_title">Custom tap and hold speed</string>
|
||||
<string name="revanced_speed_tap_and_hold_summary">Playback speed between 0-8</string>
|
||||
</patch>
|
||||
|
Reference in New Issue
Block a user