352 lines
17 KiB
Kotlin
352 lines
17 KiB
Kotlin
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.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.data.impl.toMethodWalker
|
|
import app.revanced.patcher.extensions.addInstruction
|
|
import app.revanced.patcher.extensions.addInstructions
|
|
import app.revanced.patcher.extensions.or
|
|
import app.revanced.patcher.extensions.replaceInstruction
|
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
|
|
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve
|
|
import app.revanced.patcher.patch.PatchResult
|
|
import app.revanced.patcher.patch.PatchResultSuccess
|
|
import app.revanced.patcher.patch.annotations.DependsOn
|
|
import app.revanced.patcher.patch.annotations.Patch
|
|
import app.revanced.patcher.patch.impl.BytecodePatch
|
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
|
|
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
|
|
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
|
|
import app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints.*
|
|
import app.revanced.patches.youtube.layout.sponsorblock.resource.patch.SponsorBlockResourcePatch
|
|
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
|
|
import app.revanced.patches.youtube.misc.mapping.patch.ResourceMappingResourcePatch
|
|
import app.revanced.patches.youtube.misc.playercontrols.bytecode.patch.PlayerControlsBytecodePatch
|
|
import app.revanced.patches.youtube.misc.videoid.patch.VideoIdPatch
|
|
import app.revanced.patches.youtube.layout.autocaptions.fingerprints.StartVideoInformerFingerprint
|
|
import org.jf.dexlib2.AccessFlags
|
|
import org.jf.dexlib2.Opcode
|
|
import org.jf.dexlib2.builder.MutableMethodImplementation
|
|
import org.jf.dexlib2.iface.instruction.*
|
|
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
|
import org.jf.dexlib2.iface.reference.FieldReference
|
|
import org.jf.dexlib2.iface.reference.MethodReference
|
|
import org.jf.dexlib2.iface.reference.StringReference
|
|
import org.jf.dexlib2.immutable.ImmutableMethod
|
|
import org.jf.dexlib2.immutable.ImmutableMethodParameter
|
|
import org.jf.dexlib2.util.MethodUtil
|
|
|
|
@Patch
|
|
@DependsOn(
|
|
dependencies = [PlayerControlsBytecodePatch::class, IntegrationsPatch::class, SponsorBlockResourcePatch::class, VideoIdPatch::class]
|
|
)
|
|
@Name("sponsorblock")
|
|
@Description("Integrate SponsorBlock.")
|
|
@SponsorBlockCompatibility
|
|
@Version("0.0.1")
|
|
class SponsorBlockBytecodePatch : BytecodePatch(
|
|
listOf(
|
|
PlayerControllerSetTimeReferenceFingerprint,
|
|
CreateVideoPlayerSeekbarFingerprint,
|
|
VideoTimeFingerprint,
|
|
NextGenWatchLayoutFingerprint,
|
|
AppendTimeFingerprint,
|
|
PlayerInitFingerprint,
|
|
PlayerOverlaysLayoutInitFingerprint,
|
|
ShortsPlayerConstructorFingerprint,
|
|
StartVideoInformerFingerprint
|
|
)
|
|
) {
|
|
override fun execute(data: BytecodeData): PatchResult {/*
|
|
Set current video time
|
|
*/
|
|
val referenceResult = PlayerControllerSetTimeReferenceFingerprint.result!!
|
|
val playerControllerSetTimeMethod =
|
|
data.toMethodWalker(referenceResult.method).nextMethod(referenceResult.scanResult.patternScanResult!!.startIndex, true)
|
|
.getMethod() as MutableMethod
|
|
playerControllerSetTimeMethod.addInstruction(
|
|
2,
|
|
"invoke-static {p1, p2}, Lapp/revanced/integrations/sponsorblock/PlayerController;->setCurrentVideoTime(J)V"
|
|
)
|
|
|
|
/*
|
|
Set current video time high precision
|
|
*/
|
|
val constructorFingerprint =
|
|
object : MethodFingerprint("V", null, listOf("J", "J", "J", "J", "I", "L"), null) {}
|
|
constructorFingerprint.resolve(data, VideoTimeFingerprint.result!!.classDef)
|
|
|
|
val constructor = constructorFingerprint.result!!.mutableMethod
|
|
constructor.addInstruction(
|
|
0,
|
|
"invoke-static {p1, p2}, Lapp/revanced/integrations/sponsorblock/PlayerController;->setCurrentVideoTimeHighPrecision(J)V"
|
|
)
|
|
|
|
/*
|
|
Set current video id
|
|
*/
|
|
VideoIdPatch.injectCall("Lapp/revanced/integrations/sponsorblock/PlayerController;->setCurrentVideoId(Ljava/lang/String;)V")
|
|
|
|
/*
|
|
Seekbar drawing
|
|
*/
|
|
val seekbarSignatureResult = CreateVideoPlayerSeekbarFingerprint.result!!
|
|
val seekbarMethod = seekbarSignatureResult.mutableMethod
|
|
val seekbarMethodInstructions = seekbarMethod.implementation!!.instructions
|
|
|
|
/*
|
|
Get the instance of the seekbar rectangle
|
|
*/
|
|
seekbarMethod.addInstruction(
|
|
1,
|
|
"invoke-static {v0}, Lapp/revanced/integrations/sponsorblock/PlayerController;->setSponsorBarRect(Ljava/lang/Object;)V"
|
|
)
|
|
|
|
for ((index, instruction) in seekbarMethodInstructions.withIndex()) {
|
|
if (instruction.opcode != Opcode.INVOKE_STATIC) continue
|
|
|
|
val invokeInstruction = instruction as Instruction35c
|
|
if ((invokeInstruction.reference as MethodReference).name != "round") continue
|
|
|
|
val insertIndex = index + 2
|
|
|
|
// set the thickness of the segment
|
|
seekbarMethod.addInstruction(
|
|
insertIndex,
|
|
"invoke-static {v${invokeInstruction.registerC}}, Lapp/revanced/integrations/sponsorblock/PlayerController;->setSponsorBarThickness(I)V"
|
|
)
|
|
break
|
|
}
|
|
|
|
/*
|
|
Set rectangle absolute left and right positions
|
|
*/
|
|
val drawRectangleInstructions = seekbarMethodInstructions.filter {
|
|
it is ReferenceInstruction && (it.reference as? MethodReference)?.name == "drawRect" && it is FiveRegisterInstruction
|
|
}.map { // TODO: improve code
|
|
seekbarMethodInstructions.indexOf(it) to (it as FiveRegisterInstruction).registerD
|
|
}
|
|
|
|
val (indexRight, rectangleRightRegister) = drawRectangleInstructions[0]
|
|
val (indexLeft, rectangleLeftRegister) = drawRectangleInstructions[3]
|
|
|
|
// order of operation is important here due to the code above which has to be improved
|
|
// the reason for that is that we get the index, add instructions and then the offset would be wrong
|
|
seekbarMethod.addInstruction(
|
|
indexLeft + 1,
|
|
"invoke-static {v$rectangleLeftRegister}, Lapp/revanced/integrations/sponsorblock/PlayerController;->setSponsorBarAbsoluteLeft(Landroid/graphics/Rect;)V"
|
|
)
|
|
seekbarMethod.addInstruction(
|
|
indexRight + 1,
|
|
"invoke-static {v$rectangleRightRegister}, Lapp/revanced/integrations/sponsorblock/PlayerController;->setSponsorBarAbsoluteRight(Landroid/graphics/Rect;)V"
|
|
)
|
|
|
|
/*
|
|
Draw segment
|
|
*/
|
|
val drawSegmentInstructionInsertIndex = (seekbarMethodInstructions.size - 1 - 2)
|
|
val (canvasInstance, centerY) = (seekbarMethodInstructions[drawSegmentInstructionInsertIndex] as FiveRegisterInstruction).let {
|
|
it.registerC to it.registerE
|
|
}
|
|
seekbarMethod.addInstruction(
|
|
drawSegmentInstructionInsertIndex,
|
|
"invoke-static {v$canvasInstance, v$centerY}, Lapp/revanced/integrations/sponsorblock/PlayerController;->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V"
|
|
)
|
|
|
|
/*
|
|
Set video length
|
|
*/
|
|
VideoLengthFingerprint.resolve(data, seekbarSignatureResult.classDef)
|
|
val videoLengthMethodResult = VideoLengthFingerprint.result!!
|
|
val videoLengthMethod = videoLengthMethodResult.mutableMethod
|
|
val videoLengthMethodInstructions = videoLengthMethod.implementation!!.instructions
|
|
|
|
val videoLengthRegister =
|
|
(videoLengthMethodInstructions[videoLengthMethodResult.scanResult.patternScanResult!!.endIndex - 2] as OneRegisterInstruction).registerA
|
|
val dummyRegisterForLong =
|
|
videoLengthRegister + 1 // this is required for long values since they are 64 bit wide
|
|
videoLengthMethod.addInstruction(
|
|
videoLengthMethodResult.scanResult.patternScanResult!!.endIndex,
|
|
"invoke-static {v$videoLengthRegister, v$dummyRegisterForLong}, Lapp/revanced/integrations/sponsorblock/PlayerController;->setVideoLength(J)V"
|
|
)
|
|
|
|
/*
|
|
Voting & Shield button
|
|
*/
|
|
val controlsMethodResult = PlayerControlsBytecodePatch.showPlayerControlsFingerprintResult
|
|
|
|
val controlsLayoutStubResourceId =
|
|
ResourceMappingResourcePatch.resourceMappings.single { it.type == "id" && it.name == "controls_layout_stub" }.id
|
|
val zoomOverlayResourceId =
|
|
ResourceMappingResourcePatch.resourceMappings.single { it.type == "id" && it.name == "video_zoom_overlay_stub" }.id
|
|
|
|
methods@ for (method in controlsMethodResult.mutableClass.methods) {
|
|
val instructions = method.implementation?.instructions!!
|
|
instructions@ for ((index, instruction) in instructions.withIndex()) {
|
|
// search for method which inflates the controls layout view
|
|
if (instruction.opcode != Opcode.CONST) continue@instructions
|
|
|
|
when ((instruction as NarrowLiteralInstruction).wideLiteral) {
|
|
controlsLayoutStubResourceId -> {
|
|
// replace the view with the YouTubeControlsOverlay
|
|
val moveResultInstructionIndex = index + 5
|
|
val inflatedViewRegister =
|
|
(instructions[moveResultInstructionIndex] as OneRegisterInstruction).registerA
|
|
// initialize with the player overlay object
|
|
method.addInstructions(
|
|
moveResultInstructionIndex + 1, // insert right after moving the view to the register and use that register
|
|
"""
|
|
invoke-static {v$inflatedViewRegister}, Lapp/revanced/integrations/sponsorblock/ShieldButton;->initialize(Ljava/lang/Object;)V
|
|
invoke-static {v$inflatedViewRegister}, Lapp/revanced/integrations/sponsorblock/VotingButton;->initialize(Ljava/lang/Object;)V
|
|
"""
|
|
)
|
|
}
|
|
|
|
zoomOverlayResourceId -> {
|
|
val invertVisibilityMethod =
|
|
data.toMethodWalker(method).nextMethod(index - 6, true).getMethod() as MutableMethod
|
|
// change visibility of the buttons
|
|
invertVisibilityMethod.addInstructions(
|
|
0, """
|
|
invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/ShieldButton;->changeVisibilityNegatedImmediate(Z)V
|
|
invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/VotingButton;->changeVisibilityNegatedImmediate(Z)V
|
|
""".trimIndent()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// change visibility of the buttons
|
|
PlayerControlsBytecodePatch.injectVisibilityCheckCall("Lapp/revanced/integrations/sponsorblock/ShieldButton;->changeVisibility(Z)V")
|
|
PlayerControlsBytecodePatch.injectVisibilityCheckCall("Lapp/revanced/integrations/sponsorblock/VotingButton;->changeVisibility(Z)V")
|
|
|
|
// set SegmentHelperLayout.context to the player layout instance
|
|
val instanceRegister = 0
|
|
NextGenWatchLayoutFingerprint.result!!.mutableMethod.addInstruction(
|
|
3, // after super call
|
|
"invoke-static/range {p$instanceRegister}, Lapp/revanced/integrations/sponsorblock/PlayerController;->addSkipSponsorView15(Landroid/view/View;)V"
|
|
)
|
|
|
|
// append the new time to the player layout
|
|
val appendTimeFingerprintResult = AppendTimeFingerprint.result!!
|
|
val appendTimePatternScanStartIndex = appendTimeFingerprintResult.scanResult.patternScanResult!!.startIndex
|
|
val targetRegister =
|
|
(appendTimeFingerprintResult.method.implementation!!.instructions.elementAt(appendTimePatternScanStartIndex + 1) as OneRegisterInstruction).registerA
|
|
|
|
appendTimeFingerprintResult.mutableMethod.addInstructions(
|
|
appendTimePatternScanStartIndex + 2, """
|
|
invoke-static {v$targetRegister}, Lapp/revanced/integrations/sponsorblock/SponsorBlockUtils;->appendTimeWithoutSegments(Ljava/lang/String;)Ljava/lang/String;
|
|
move-result-object v$targetRegister
|
|
"""
|
|
)
|
|
|
|
// initialize the player controller
|
|
val initFingerprintResult = PlayerInitFingerprint.result!!
|
|
val initInstanceRegister = 0
|
|
initFingerprintResult.mutableClass.methods.first { MethodUtil.isConstructor(it) }.addInstruction(
|
|
4, // after super class invoke
|
|
"invoke-static {v$initInstanceRegister}, Lapp/revanced/integrations/sponsorblock/PlayerController;->onCreate(Ljava/lang/Object;)V"
|
|
)
|
|
|
|
// initialize the sponsorblock view
|
|
PlayerOverlaysLayoutInitFingerprint.result!!.mutableMethod.addInstruction(
|
|
6, // after inflating the view
|
|
"invoke-static {p0}, Lapp/revanced/integrations/sponsorblock/player/ui/SponsorBlockView;->initialize(Ljava/lang/Object;)V"
|
|
)
|
|
|
|
// lastly create hooks for the player controller
|
|
|
|
// get original seek method
|
|
SeekFingerprint.resolve(data, initFingerprintResult.classDef)
|
|
val seekFingerprintResultMethod = SeekFingerprint.result!!.method
|
|
// get enum type for the seek helper method
|
|
val seekSourceEnumType = seekFingerprintResultMethod.parameterTypes[1].toString()
|
|
|
|
// create helper method
|
|
val seekHelperMethod = ImmutableMethod(
|
|
seekFingerprintResultMethod.definingClass,
|
|
"seekHelper",
|
|
listOf(ImmutableMethodParameter("J", null, "time")),
|
|
"Z",
|
|
AccessFlags.PUBLIC or AccessFlags.FINAL,
|
|
null, null,
|
|
MutableMethodImplementation(4)
|
|
).toMutable()
|
|
|
|
// insert helper method instructions
|
|
seekHelperMethod.addInstructions(
|
|
0,
|
|
"""
|
|
sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
|
|
invoke-virtual {p0, p1, p2, v0}, ${seekFingerprintResultMethod.definingClass}->${seekFingerprintResultMethod.name}(J$seekSourceEnumType)Z
|
|
move-result p1
|
|
return p1
|
|
"""
|
|
)
|
|
|
|
// add the helper method to the original class
|
|
initFingerprintResult.mutableClass.methods.add(seekHelperMethod)
|
|
|
|
// get rectangle field name
|
|
RectangleFieldInvalidatorFingerprint.resolve(data, seekbarSignatureResult.classDef)
|
|
val rectangleFieldInvalidatorInstructions =
|
|
RectangleFieldInvalidatorFingerprint.result!!.method.implementation!!.instructions
|
|
val rectangleFieldName =
|
|
((rectangleFieldInvalidatorInstructions.elementAt(rectangleFieldInvalidatorInstructions.count() - 3) as ReferenceInstruction).reference as FieldReference).name
|
|
|
|
// get the player controller class from the integrations
|
|
val playerControllerMethods =
|
|
data.proxy(data.classes.first { it.type.endsWith("PlayerController;") }).resolve().methods
|
|
|
|
// get the method which contain the "replaceMe" strings
|
|
val replaceMeMethods =
|
|
playerControllerMethods.filter { it.name == "onCreate" || it.name == "setSponsorBarRect" }
|
|
|
|
fun MutableMethod.replaceStringInstruction(index: Int, instruction: Instruction, with: String) {
|
|
val register = (instruction as OneRegisterInstruction).registerA
|
|
this.replaceInstruction(
|
|
index, "const-string v$register, \"$with\""
|
|
)
|
|
}
|
|
|
|
// replace the "replaceMeWith*" strings
|
|
for (method in replaceMeMethods) {
|
|
for ((index, it) in method.implementation!!.instructions.withIndex()) {
|
|
if (it.opcode.ordinal != Opcode.CONST_STRING.ordinal) continue
|
|
|
|
when (((it as ReferenceInstruction).reference as StringReference).string) {
|
|
"replaceMeWithsetSponsorBarRect" ->
|
|
method.replaceStringInstruction(index, it, rectangleFieldName)
|
|
|
|
"replaceMeWithsetMillisecondMethod" ->
|
|
method.replaceStringInstruction(index, it, "seekHelper")
|
|
}
|
|
}
|
|
}
|
|
|
|
val startVideoInformerMethod = StartVideoInformerFingerprint.result!!.mutableMethod
|
|
startVideoInformerMethod.addInstructions(
|
|
0, """
|
|
const/4 v0, 0x0
|
|
sput-boolean v0, Lapp/revanced/integrations/settings/SettingsEnum;->shorts_playing:Z
|
|
"""
|
|
)
|
|
|
|
val shortsPlayerConstructorMethod = ShortsPlayerConstructorFingerprint.result!!.mutableMethod
|
|
|
|
shortsPlayerConstructorMethod.addInstructions(
|
|
0, """
|
|
const/4 v0, 0x1
|
|
sput-boolean v0, Lapp/revanced/integrations/settings/SettingsEnum;->shorts_playing:Z
|
|
"""
|
|
)
|
|
|
|
// TODO: isSBChannelWhitelisting implementation
|
|
|
|
return PatchResultSuccess()
|
|
}
|
|
}
|