2022-06-28 23:43:07 +02:00
|
|
|
package app.revanced.patches.youtube.misc.customplaybackspeed.patch
|
|
|
|
|
|
|
|
import app.revanced.patcher.annotation.Description
|
|
|
|
import app.revanced.patcher.annotation.Name
|
|
|
|
import app.revanced.patcher.annotation.Version
|
|
|
|
import app.revanced.patcher.data.impl.BytecodeData
|
|
|
|
import app.revanced.patcher.extensions.addInstructions
|
|
|
|
import app.revanced.patcher.extensions.replaceInstruction
|
2022-09-22 07:17:10 +02:00
|
|
|
import app.revanced.patcher.patch.*
|
2022-08-03 03:53:35 +02:00
|
|
|
import app.revanced.patcher.patch.annotations.DependsOn
|
2022-06-30 02:48:18 +02:00
|
|
|
import app.revanced.patcher.patch.annotations.Patch
|
|
|
|
import app.revanced.patcher.patch.impl.BytecodePatch
|
2022-06-28 23:43:07 +02:00
|
|
|
import app.revanced.patches.youtube.misc.customplaybackspeed.annotations.CustomPlaybackSpeedCompatibility
|
|
|
|
import app.revanced.patches.youtube.misc.customplaybackspeed.fingerprints.SpeedArrayGeneratorFingerprint
|
|
|
|
import app.revanced.patches.youtube.misc.customplaybackspeed.fingerprints.SpeedLimiterFingerprint
|
2022-09-22 07:17:10 +02:00
|
|
|
import app.revanced.patches.youtube.misc.customplaybackspeed.fingerprints.VideoSpeedPatchFingerprint
|
2022-06-28 23:43:07 +02:00
|
|
|
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
|
2022-09-26 04:12:09 +02:00
|
|
|
import app.revanced.patches.youtube.misc.mapping.patch.ResourceMappingResourcePatch
|
2022-09-22 07:17:10 +02:00
|
|
|
import org.jf.dexlib2.builder.instruction.BuilderArrayPayload
|
2022-06-28 23:43:07 +02:00
|
|
|
import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction
|
|
|
|
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
|
2022-06-30 02:48:18 +02:00
|
|
|
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
|
2022-06-28 23:43:07 +02:00
|
|
|
import org.jf.dexlib2.iface.reference.FieldReference
|
2022-06-30 02:48:18 +02:00
|
|
|
import org.jf.dexlib2.iface.reference.MethodReference
|
2022-09-22 07:17:10 +02:00
|
|
|
import java.util.stream.DoubleStream
|
|
|
|
import kotlin.math.roundToInt
|
2022-06-28 23:43:07 +02:00
|
|
|
|
|
|
|
@Patch
|
|
|
|
@Name("custom-playback-speed")
|
2022-07-17 12:36:46 +02:00
|
|
|
@Description("Adds more video playback speed options.")
|
2022-09-26 04:12:09 +02:00
|
|
|
@DependsOn([IntegrationsPatch::class, ResourceMappingResourcePatch::class])
|
2022-06-28 23:43:07 +02:00
|
|
|
@CustomPlaybackSpeedCompatibility
|
|
|
|
@Version("0.0.1")
|
|
|
|
class CustomPlaybackSpeedPatch : BytecodePatch(
|
|
|
|
listOf(
|
2022-09-22 07:17:10 +02:00
|
|
|
SpeedArrayGeneratorFingerprint, SpeedLimiterFingerprint, VideoSpeedPatchFingerprint
|
2022-06-28 23:43:07 +02:00
|
|
|
)
|
|
|
|
) {
|
|
|
|
|
|
|
|
override fun execute(data: BytecodeData): PatchResult {
|
2022-08-22 01:59:43 +02:00
|
|
|
//TODO: include setting to skip remembering the new speed
|
|
|
|
|
2022-09-22 07:17:10 +02:00
|
|
|
val speedLimitMin = minVideoSpeed!!.toFloat()
|
|
|
|
val speedLimitMax = maxVideoSpeed!!.toFloat().coerceAtLeast(speedLimitMin)
|
|
|
|
val speedsGranularity = videoSpeedsGranularity!!.toFloat()
|
|
|
|
|
2022-06-28 23:43:07 +02:00
|
|
|
val arrayGenMethod = SpeedArrayGeneratorFingerprint.result?.mutableMethod!!
|
|
|
|
val arrayGenMethodImpl = arrayGenMethod.implementation!!
|
|
|
|
|
|
|
|
val sizeCallIndex = arrayGenMethodImpl.instructions
|
|
|
|
.indexOfFirst { ((it as? ReferenceInstruction)?.reference as? MethodReference)?.name == "size" }
|
|
|
|
|
|
|
|
if (sizeCallIndex == -1) return PatchResultError("Couldn't find call to size()")
|
|
|
|
|
|
|
|
val sizeCallResultRegister =
|
|
|
|
(arrayGenMethodImpl.instructions.elementAt(sizeCallIndex + 1) as OneRegisterInstruction).registerA
|
|
|
|
|
|
|
|
arrayGenMethod.replaceInstruction(
|
|
|
|
sizeCallIndex + 1,
|
|
|
|
"const/4 v$sizeCallResultRegister, 0x0"
|
|
|
|
)
|
|
|
|
|
|
|
|
val (arrayLengthConstIndex, arrayLengthConst) = arrayGenMethodImpl.instructions.withIndex()
|
|
|
|
.first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 7 }
|
|
|
|
|
|
|
|
val arrayLengthConstDestination = (arrayLengthConst as OneRegisterInstruction).registerA
|
|
|
|
|
2022-07-14 18:42:22 +02:00
|
|
|
val videoSpeedsArrayType = "Lapp/revanced/integrations/patches/VideoSpeedPatch;->videoSpeeds:[F"
|
2022-06-28 23:43:07 +02:00
|
|
|
|
|
|
|
arrayGenMethod.addInstructions(
|
|
|
|
arrayLengthConstIndex + 1,
|
|
|
|
"""
|
|
|
|
sget-object v$arrayLengthConstDestination, $videoSpeedsArrayType
|
|
|
|
array-length v$arrayLengthConstDestination, v$arrayLengthConstDestination
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
|
|
|
val (originalArrayFetchIndex, originalArrayFetch) = arrayGenMethodImpl.instructions.withIndex()
|
|
|
|
.first {
|
|
|
|
val reference = ((it.value as? ReferenceInstruction)?.reference as? FieldReference)
|
|
|
|
reference?.definingClass?.contains("PlayerConfigModel") ?: false &&
|
|
|
|
reference?.type == "[F"
|
|
|
|
}
|
|
|
|
|
|
|
|
val originalArrayFetchDestination = (originalArrayFetch as OneRegisterInstruction).registerA
|
|
|
|
|
|
|
|
arrayGenMethod.replaceInstruction(
|
|
|
|
originalArrayFetchIndex,
|
|
|
|
"sget-object v$originalArrayFetchDestination, $videoSpeedsArrayType"
|
|
|
|
)
|
|
|
|
|
2022-09-22 08:38:49 +02:00
|
|
|
val limiterMethod = SpeedLimiterFingerprint.result?.mutableMethod!!
|
2022-06-28 23:43:07 +02:00
|
|
|
val limiterMethodImpl = limiterMethod.implementation!!
|
|
|
|
|
|
|
|
val (limiterMinConstIndex, limiterMinConst) = limiterMethodImpl.instructions.withIndex()
|
|
|
|
.first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 0.25f.toRawBits() }
|
|
|
|
val (limiterMaxConstIndex, limiterMaxConst) = limiterMethodImpl.instructions.withIndex()
|
|
|
|
.first { (it.value as? NarrowLiteralInstruction)?.narrowLiteral == 2.0f.toRawBits() }
|
|
|
|
|
|
|
|
val limiterMinConstDestination = (limiterMinConst as OneRegisterInstruction).registerA
|
|
|
|
val limiterMaxConstDestination = (limiterMaxConst as OneRegisterInstruction).registerA
|
|
|
|
|
|
|
|
fun hexFloat(float: Float): String = "0x%08x".format(float.toRawBits())
|
|
|
|
|
|
|
|
limiterMethod.replaceInstruction(
|
|
|
|
limiterMinConstIndex,
|
|
|
|
"const/high16 v$limiterMinConstDestination, ${hexFloat(speedLimitMin)}"
|
|
|
|
)
|
|
|
|
limiterMethod.replaceInstruction(
|
|
|
|
limiterMaxConstIndex,
|
|
|
|
"const/high16 v$limiterMaxConstDestination, ${hexFloat(speedLimitMax)}"
|
|
|
|
)
|
|
|
|
|
2022-09-22 07:17:10 +02:00
|
|
|
val constructorResult = VideoSpeedPatchFingerprint.result!!
|
|
|
|
val constructor = constructorResult.mutableMethod
|
|
|
|
val implementation = constructor.implementation!!
|
|
|
|
|
|
|
|
val stepsGranularity = 8F
|
|
|
|
val step = speedLimitMax
|
|
|
|
.minus(speedLimitMin) // calculate the range of the speeds
|
|
|
|
.div(speedsGranularity)
|
|
|
|
.times(stepsGranularity)
|
|
|
|
.roundToInt()
|
|
|
|
.div(stepsGranularity)// round to nearest multiple of stepsGranularity
|
|
|
|
.coerceAtLeast(1 / stepsGranularity) // ensure steps are at least 1/8th of the step granularity
|
|
|
|
|
2022-09-22 08:38:49 +02:00
|
|
|
val videoSpeedsArray = buildList<Number> {
|
|
|
|
DoubleStream
|
|
|
|
.iterate(speedLimitMin.toDouble()) { it + step } // create a stream of speeds
|
|
|
|
.let { speedStream ->
|
|
|
|
for (speed in speedStream) {
|
|
|
|
if (speed > speedLimitMax) break
|
|
|
|
add(speed.toFloat().toRawBits())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-09-22 07:17:10 +02:00
|
|
|
|
|
|
|
// adjust the new array of speeds size
|
|
|
|
constructor.replaceInstruction(
|
|
|
|
0,
|
|
|
|
"const/16 v0, ${videoSpeedsArray.size}"
|
|
|
|
)
|
|
|
|
|
|
|
|
// create the payload with the new speeds
|
|
|
|
val arrayPayloadIndex = implementation.instructions.size - 1
|
|
|
|
implementation.replaceInstruction(
|
|
|
|
arrayPayloadIndex,
|
|
|
|
BuilderArrayPayload(
|
|
|
|
4,
|
|
|
|
videoSpeedsArray
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2022-06-28 23:43:07 +02:00
|
|
|
return PatchResultSuccess()
|
|
|
|
}
|
2022-09-22 07:17:10 +02:00
|
|
|
|
|
|
|
companion object : OptionsContainer() {
|
|
|
|
private fun String?.validate(max: Int? = null) = this?.toFloatOrNull() != null &&
|
|
|
|
toFloat().let { float ->
|
|
|
|
float > 0 && max?.let { max -> float <= max } ?: true
|
|
|
|
}
|
|
|
|
|
|
|
|
val videoSpeedsGranularity by option(
|
|
|
|
PatchOption.StringOption(
|
|
|
|
"granularity",
|
|
|
|
"16",
|
|
|
|
"Video speed granularity",
|
|
|
|
"The granularity of the video speeds. The higher the value, the more speeds will be available.",
|
|
|
|
true
|
|
|
|
) {
|
|
|
|
it.validate()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
val minVideoSpeed by option(
|
|
|
|
PatchOption.StringOption(
|
|
|
|
"min",
|
|
|
|
"0.25",
|
|
|
|
"Minimum video speed",
|
|
|
|
"The minimum video speed.",
|
|
|
|
true
|
|
|
|
) {
|
|
|
|
it.validate()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
val maxVideoSpeed by option(
|
|
|
|
PatchOption.StringOption(
|
|
|
|
"max",
|
|
|
|
"5.0",
|
|
|
|
"Maximum video speed",
|
|
|
|
"The maximum video speed. Must be greater than the minimum video speed and smaller than 5.",
|
|
|
|
true
|
|
|
|
) {
|
|
|
|
it.validate(5)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2022-06-28 23:43:07 +02:00
|
|
|
}
|