feat: sponsorblock patch (#101)

* chore(release): 2.5.1-dev.1 [skip ci]

## [2.5.1-dev.1](https://github.com/revanced/revanced-patches/compare/v2.5.0...v2.5.1-dev.1) (2022-06-30)

### Bug Fixes

* freezing panels when watching video in fullscreen ([#89](https://github.com/revanced/revanced-patches/issues/89)) ([f5d4f6c](f5d4f6c341))
* invalid version in compatibility annotation ([#90](https://github.com/revanced/revanced-patches/issues/90)) ([df43547](df435475cd))

* feat: migrate to breaking changes of patcher

* chore(release): 2.6.0-dev.1 [skip ci]

# [2.6.0-dev.1](https://github.com/revanced/revanced-patches/compare/v2.5.1-dev.1...v2.6.0-dev.1) (2022-07-02)

### Features

* migrate to breaking changes of patcher ([a116852](a11685263f))

* refactor: add package for fingerprints

* feat: partial `sponsorblock` patch

* feat: add `Name` annotation to `sponsorblock-resource-patch`

* fix: `sponsorblock-resource-patch`

* refactor: remove unused resources

* refactor: remove `locale-config-fix` dependency

* fix: broken fingerprints

* feat: general fixes on `sponsorblock` patch

* feat: more fixes on `sponsorblock` patch

* feat: update `sponsorblock` patch to 17.26.35

* style: use better wording and fix spelling mistakes

* fix: finish `sponsorblock` patch

Co-authored-by: semantic-release-bot <semantic-release-bot@martynus.net>
This commit is contained in:
oSumAtrIX
2022-07-18 01:17:03 +02:00
committed by GitHub
parent 8f06ea9115
commit 36af4cc14f
27 changed files with 1149 additions and 7 deletions

View File

@ -0,0 +1,13 @@
package app.revanced.patches.youtube.layout.sponsorblock.annotations
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package
@Compatibility(
[Package(
"com.google.android.youtube", arrayOf("17.22.36", "17.26.35", "17.27.39")
)]
)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
internal annotation class SponsorBlockCompatibility

View File

@ -0,0 +1,41 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.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.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
@Name("append-time-fingerprint")
@MatchingMethod(
"Liet;", "e"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object AppendTimeFingerprint : MethodFingerprint(
"V",
AccessFlags.PUBLIC or AccessFlags.FINAL,
listOf("L", "L", "L"),
listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT,
Opcode.IF_NEZ,
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
)
)

View File

@ -0,0 +1,21 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
@Name("create-video-player-seekbar-fingerprint")
@MatchingMethod(
"Lfcm;", "onDraw"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object CreateVideoPlayerSeekbarFingerprint : MethodFingerprint(
"V", null, null,
null,
listOf("timed_markers_width")
)

View File

@ -0,0 +1,24 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
import org.jf.dexlib2.util.MethodUtil
@Name("next-gen-watch-layout-fingerprint")
@MatchingMethod(
"LNextGenWatchLayout;", "<init>"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object NextGenWatchLayoutFingerprint : MethodFingerprint(
"V", // constructors return void, in favour of speed of matching, this fingerprint has been added
null,
null,
null,
customFingerprint = { methodDef -> MethodUtil.isConstructor(methodDef) && methodDef.parameterTypes.size == 3 && methodDef.definingClass.endsWith("NextGenWatchLayout;") }
)

View File

@ -0,0 +1,22 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
import org.jf.dexlib2.Opcode
@Name("player-controller-set-time-reference-fingerprint")
@MatchingMethod(
"Lxqm;", "<init>"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object PlayerControllerSetTimeReferenceFingerprint : MethodFingerprint(
null, null, null,
listOf(Opcode.INVOKE_DIRECT_RANGE, Opcode.IGET_OBJECT),
listOf("Media progress reported outside media playback: ")
)

View File

@ -0,0 +1,23 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
@Name("player-init-fingerprint")
@MatchingMethod(
"Laajv;", "aG"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object PlayerInitFingerprint : MethodFingerprint(
null, null, null,
null,
strings = listOf(
"playVideo called on player response with no videoStreamingData."
),
)

View File

@ -0,0 +1,22 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
@Name("player-overlays-layout-init-fingerprint")
@MatchingMethod(
"Lihh;", "u"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object PlayerOverlaysLayoutInitFingerprint : MethodFingerprint(
null, null, null,
null,
null,
{ methodDef -> methodDef.returnType.endsWith("YouTubePlayerOverlaysLayout;") }
)

View File

@ -0,0 +1,37 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.iface.reference.MethodReference
@Name("rectangle-field-invalidator-fingerprint")
@MatchingMethod(
"Lfcm;", "kY"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object RectangleFieldInvalidatorFingerprint : MethodFingerprint(
"V",
null,
null,
null,
null,
custom@{ methodDef ->
val instructions = methodDef.implementation?.instructions!!
val instructionCount = instructions.count()
// the method has definitely more than 5 instructions
if (instructionCount < 5) return@custom false
val referenceInstruction = instructions.elementAt(instructionCount - 2) // the second to last instruction
val reference = ((referenceInstruction as? ReferenceInstruction)?.reference as? MethodReference)
reference?.parameterTypes?.size == 1 && reference.name == "invalidate" // the reference is the invalidate(..) method
}
)

View File

@ -0,0 +1,23 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
@Name("seek-fingerprint")
@MatchingMethod(
"Laajv;", "af"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object SeekFingerprint : MethodFingerprint(
null,
null,
null,
null,
listOf("Attempting to seek during an ad")
)

View File

@ -0,0 +1,19 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
@Name("show-player-controls-fingerprint")
@MatchingMethod(
"LYouTubeControlsOverlay;", "ac"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object ShowPlayerControlsFingerprint : MethodFingerprint(
"V", null, listOf("Z","Z"), null, null
)

View File

@ -0,0 +1,34 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
import org.jf.dexlib2.Opcode
@Name("video-length-fingerprint")
@MatchingMethod(
"Lyfh;", "z"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object VideoLengthFingerprint : MethodFingerprint(
null, null, null,
listOf(
Opcode.MOVE_RESULT_WIDE,
Opcode.CMP_LONG,
Opcode.IF_LEZ,
Opcode.IGET_OBJECT,
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_WIDE,
Opcode.GOTO,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_WIDE,
Opcode.CONST_4,
Opcode.INVOKE_VIRTUAL
)
)

View File

@ -0,0 +1,20 @@
package app.revanced.patches.youtube.layout.sponsorblock.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.DirectPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
@Name("video-time-fingerprint")
@MatchingMethod(
"Lwyh;", "<init>"
)
@DirectPatternScanMethod
@SponsorBlockCompatibility
@Version("0.0.1")
object VideoTimeFingerprint : MethodFingerprint(
null, null, null, null,
listOf("MedialibPlayerTimeInfo{currentPositionMillis=")
)

View File

@ -0,0 +1,335 @@
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.utils.MethodFingerprintUtils.resolve
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.Dependencies
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.ResourceIdMappingProviderResourcePatch
import app.revanced.patches.youtube.misc.videoid.patch.VideoIdPatch
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
@Dependencies(
dependencies = [IntegrationsPatch::class, ResourceIdMappingProviderResourcePatch::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
)
) {
override fun execute(data: BytecodeData): PatchResult {/*
Set current video time
*/
val referenceResult = PlayerControllerSetTimeReferenceFingerprint.result!!
val playerControllerSetTimeMethod =
data.toMethodWalker(referenceResult.method).nextMethod(referenceResult.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 - 1,
"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.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.patternScanResult!!.endIndex,
"invoke-static {v$videoLengthRegister, v$dummyRegisterForLong}, Lapp/revanced/integrations/sponsorblock/PlayerController;->setVideoLength(J)V"
)
/*
Voting & Shield button
*/
ShowPlayerControlsFingerprint.resolve(data, data.classes.find { it.type.endsWith("YouTubeControlsOverlay;") }!!)
val controlsMethodResult = ShowPlayerControlsFingerprint.result!!
val controlsLayoutStubResourceId =
ResourceIdMappingProviderResourcePatch.resourceMappings.single { it.type == "id" && it.name == "controls_layout_stub" }.id
val zoomOverlayResourceId =
ResourceIdMappingProviderResourcePatch.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
controlsMethodResult.mutableMethod.addInstructions(
0, """
invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/ShieldButton;->changeVisibility(Z)V
invoke-static {p1}, Lapp/revanced/integrations/sponsorblock/VotingButton;->changeVisibility(Z)V
""".trimIndent()
)
// 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.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")
}
}
}
// TODO: isSBChannelWhitelisting implementation
return PatchResultSuccess()
}
}

View File

@ -0,0 +1,128 @@
package app.revanced.patches.youtube.layout.sponsorblock.resource.patch
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.impl.DomFileEditor
import app.revanced.patcher.data.impl.ResourceData
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.Dependencies
import app.revanced.patcher.patch.impl.ResourcePatch
import app.revanced.patches.youtube.layout.sponsorblock.annotations.SponsorBlockCompatibility
import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
import java.io.OutputStream
import java.nio.file.Files
@Name("sponsorblock-resource-patch")
@SponsorBlockCompatibility
@Dependencies([FixLocaleConfigErrorPatch::class])
@Version("0.0.1")
class SponsorBlockResourcePatch : ResourcePatch() {
override fun execute(data: ResourceData): PatchResult {
val classLoader = this.javaClass.classLoader
/*
merge SponsorBlock strings to main strings
*/
val stringsResourcePath = "values/strings.xml"
val stringsResourceInputStream = classLoader.getResourceAsStream("sponsorblock/$stringsResourcePath")!!
// copy nodes from the resources node to the real resource node
"resources".copyXmlNode(
data.xmlEditor[stringsResourceInputStream, OutputStream.nullOutputStream()],
data.xmlEditor["res/$stringsResourcePath"]
).close() // close afterwards
/*
merge SponsorBlock drawables to main drawables
*/
val drawables = "drawable" to arrayOf(
"ic_sb_adjust",
"ic_sb_compare",
"ic_sb_edit",
"ic_sb_logo",
"ic_sb_publish",
"ic_sb_voting"
)
val layouts = "layout" to arrayOf(
"inline_sponsor_overlay", "new_segment", "skip_sponsor_button"
)
// collect resources
val xmlResources = arrayOf(drawables, layouts)
// write resources
xmlResources.forEach { (path, resourceNames) ->
resourceNames.forEach { name ->
val relativePath = "$path/$name.xml"
Files.copy(
classLoader.getResourceAsStream("sponsorblock/$relativePath")!!,
data["res"].resolve(relativePath).toPath()
)
}
}
/*
merge xml nodes from the host to their real xml files
*/
// collect all host resources
val hostingXmlResources = mapOf("layout" to arrayOf("youtube_controls_layout"))
// copy nodes from host resources to their real xml files
hostingXmlResources.forEach { (path, resources) ->
resources.forEach { resource ->
val hostingResourceStream = classLoader.getResourceAsStream("sponsorblock/host/$path/$resource.xml")!!
val targetXmlEditor = data.xmlEditor["res/$path/$resource.xml"]
"RelativeLayout".copyXmlNode(
data.xmlEditor[hostingResourceStream, OutputStream.nullOutputStream()],
targetXmlEditor
).also {
val children = targetXmlEditor.file.getElementsByTagName("RelativeLayout").item(0).childNodes
// Replace the startOf with the voting button view so that the button does not overlap
for (i in 1 until children.length) {
val view = children.item(i)
// Replace the attribute for a specific node only
if (!view.attributes.getNamedItem("android:id").nodeValue.endsWith("live_chat_overlay_button")) continue
// voting button id from the voting button view from the youtube_controls_layout.xml host file
val votingButtonId = "@+id/voting_button"
view.attributes.getNamedItem("android:layout_toStartOf").nodeValue = votingButtonId
break
}
}.close() // close afterwards
}
}
return PatchResultSuccess()
}
/**
* Copies the specified node of the source [DomFileEditor] to the target [DomFileEditor].
* @param source the source [DomFileEditor].
* @param target the target [DomFileEditor]-
*/
private fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoCloseable {
val hostNodes = source.file.getElementsByTagName(this).item(0).childNodes
val destinationResourceFile = target.file
val destinationNode = destinationResourceFile.getElementsByTagName(this).item(0)
for (index in 0 until hostNodes.length) {
val node = hostNodes.item(index).cloneNode(true)
destinationResourceFile.adoptNode(node)
destinationNode.appendChild(node)
}
return AutoCloseable {
source.close()
target.close()
}
}
}

View File

@ -5,14 +5,16 @@ 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.fingerprint.method.impl.MethodFingerprintResult
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.Dependencies
import app.revanced.patcher.patch.impl.BytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.videoid.annotation.VideoIdCompatibility
import app.revanced.patches.youtube.misc.videoid.fingerprint.VideoIdFingerprint
import org.jf.dexlib2.iface.instruction.formats.Instruction11x
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
@Name("video-id-hook")
@Description("hook to detect when the video id changes")
@ -25,12 +27,21 @@ class VideoIdPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
result = VideoIdFingerprint.result!!
insertMethod = result.mutableMethod
videoIdRegister =
(insertMethod.implementation!!.instructions[result.patternScanResult!!.endIndex + 1] as OneRegisterInstruction).registerA
injectCall("Lapp/revanced/integrations/videoplayer/VideoInformation;->setCurrentVideoId(Ljava/lang/String;)V")
return PatchResultSuccess()
}
companion object {
private lateinit var result: MethodFingerprintResult
private var videoIdRegister: Int = 0
private lateinit var insertMethod: MutableMethod
private var offset = 2
/**
@ -40,12 +51,7 @@ class VideoIdPatch : BytecodePatch(
fun injectCall(
methodDescriptor: String
) {
val result = VideoIdFingerprint.result!!
val method = result.mutableMethod
val videoIdRegister =
(method.implementation!!.instructions[result.patternScanResult!!.endIndex + 1] as Instruction11x).registerA
method.addInstructions(
insertMethod.addInstructions(
result.patternScanResult!!.endIndex + offset, // after the move-result-object
"invoke-static {v$videoIdRegister}, $methodDescriptor"
)