diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/annotations/CustomPlaybackSpeedCompatibility.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/annotations/CustomPlaybackSpeedCompatibility.kt new file mode 100644 index 000000000..25e58c0cc --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/annotations/CustomPlaybackSpeedCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.youtube.misc.customplaybackspeed.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.google.android.youtube", arrayOf("17.24.34") + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class CustomPlaybackSpeedCompatibility diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/SpeedArrayGeneratorFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/SpeedArrayGeneratorFingerprint.kt new file mode 100644 index 000000000..9d16c87bf --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/SpeedArrayGeneratorFingerprint.kt @@ -0,0 +1,33 @@ +package app.revanced.patches.youtube.misc.customplaybackspeed.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod +import app.revanced.patches.youtube.misc.customplaybackspeed.annotations.CustomPlaybackSpeedCompatibility +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + +@Name("speed-array-generator-fingerprint") +@MatchingMethod( + "Lzdj;", "d" +) +@FuzzyPatternScanMethod(2) +@CustomPlaybackSpeedCompatibility +@Version("0.0.1") +object SpeedArrayGeneratorFingerprint : MethodFingerprint( + "[L", + AccessFlags.PUBLIC or AccessFlags.STATIC, + null, + listOf( + Opcode.IF_NEZ, + Opcode.SGET_OBJECT, + Opcode.GOTO, + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.IGET_OBJECT, + ), + listOf("0.0#") +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/SpeedLimiterFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/SpeedLimiterFingerprint.kt new file mode 100644 index 000000000..c46ce9323 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/fingerprints/SpeedLimiterFingerprint.kt @@ -0,0 +1,34 @@ +package app.revanced.patches.youtube.misc.customplaybackspeed.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod +import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod +import app.revanced.patches.youtube.misc.customplaybackspeed.annotations.CustomPlaybackSpeedCompatibility +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.Opcode + +@Name("speed-limiter-fingerprint") +@MatchingMethod( + "Lxgy;", "y" +) +@FuzzyPatternScanMethod(2) +@CustomPlaybackSpeedCompatibility +@Version("0.0.1") +object SpeedLimiterFingerprint : MethodFingerprint( + "V", + AccessFlags.PUBLIC or AccessFlags.FINAL, + listOf("F"), + listOf( + Opcode.INVOKE_STATIC, + Opcode.MOVE_RESULT, + Opcode.IF_EQZ, + Opcode.CONST_HIGH16, + Opcode.GOTO, + Opcode.CONST_HIGH16, + Opcode.CONST_HIGH16, + Opcode.INVOKE_STATIC, + ), +) diff --git a/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/patch/CustomPlaybackSpeedPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/patch/CustomPlaybackSpeedPatch.kt new file mode 100644 index 000000000..d11c7e005 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/misc/customplaybackspeed/patch/CustomPlaybackSpeedPatch.kt @@ -0,0 +1,110 @@ +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 +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.patch.annotations.Dependencies +import app.revanced.patcher.patch.impl.BytecodePatch +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.PatchResultError +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 +import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.instruction.NarrowLiteralInstruction +import org.jf.dexlib2.iface.instruction.OneRegisterInstruction +import org.jf.dexlib2.iface.reference.MethodReference +import org.jf.dexlib2.iface.reference.FieldReference + +@Patch +@Name("custom-playback-speed") +@Description("Allows to change the default playback speed options") +@Dependencies(dependencies = [IntegrationsPatch::class]) +@CustomPlaybackSpeedCompatibility +@Version("0.0.1") +class CustomPlaybackSpeedPatch : BytecodePatch( + listOf( + SpeedArrayGeneratorFingerprint, SpeedLimiterFingerprint + ) +) { + + override fun execute(data: BytecodeData): PatchResult { + 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 + + val videoSpeedsArrayType = "Lapp/revanced/integrations/videoplayer/videosettings/VideoSpeed;->videoSpeeds:[F" + + 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" + ) + + val limiterMethod = SpeedLimiterFingerprint.result?.mutableMethod!!; + val limiterMethodImpl = limiterMethod.implementation!! + + val speedLimitMin = 0.25f + val speedLimitMax = 100f + + 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)}" + ) + + return PatchResultSuccess() + } +}