diff --git a/src/main/kotlin/app/revanced/patches/music/utils/fingerprints/SeekBarConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/fingerprints/SeekBarConstructorFingerprint.kt new file mode 100644 index 000000000..cee1568f5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/fingerprints/SeekBarConstructorFingerprint.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.music.utils.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch.Companion.InlineTimeBarAdBreakMarkerColor +import app.revanced.util.bytecode.isWideLiteralExists + +object SeekBarConstructorFingerprint : MethodFingerprint( + returnType = "V", + customFingerprint = { methodDef, _ -> methodDef.isWideLiteralExists(InlineTimeBarAdBreakMarkerColor) } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/resourceid/patch/SharedResourceIdPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/resourceid/patch/SharedResourceIdPatch.kt index a75acfeb2..669b8bbc0 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/resourceid/patch/SharedResourceIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/resourceid/patch/SharedResourceIdPatch.kt @@ -22,6 +22,7 @@ class SharedResourceIdPatch : ResourcePatch { var ColorGrey: Long = -1 var DialogSolid: Long = -1 var DisabledIconAlpha: Long = -1 + var InlineTimeBarAdBreakMarkerColor: Long = -1 var IsTablet: Long = -1 var MusicMenuLikeButtons: Long = -1 var MusicNotifierShelf: Long = -1 @@ -44,6 +45,7 @@ class SharedResourceIdPatch : ResourcePatch { ColorGrey = find(COLOR, "ytm_color_grey_12") DialogSolid = find(STYLE, "Theme.YouTubeMusic.Dialog.Solid") DisabledIconAlpha = find(DIMEN, "disabled_icon_alpha") + InlineTimeBarAdBreakMarkerColor = find(COLOR, "inline_time_bar_ad_break_marker_color") IsTablet = find(BOOL, "is_tablet") MusicMenuLikeButtons = find(LAYOUT, "music_menu_like_buttons") MusicNotifierShelf = find(LAYOUT, "music_notifier_shelf") diff --git a/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/fingerprints/MusicPlaybackControlsTimeBarDrawFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/fingerprints/MusicPlaybackControlsTimeBarDrawFingerprint.kt new file mode 100644 index 000000000..62f8285b5 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/fingerprints/MusicPlaybackControlsTimeBarDrawFingerprint.kt @@ -0,0 +1,11 @@ +package app.revanced.patches.music.utils.sponsorblock.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object MusicPlaybackControlsTimeBarDrawFingerprint : MethodFingerprint( + returnType = "V", + customFingerprint = { methodDef, _ -> + methodDef.definingClass.endsWith("/MusicPlaybackControlsTimeBar;") + && methodDef.name == "draw" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/fingerprints/MusicPlaybackControlsTimeBarOnMeasureFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/fingerprints/MusicPlaybackControlsTimeBarOnMeasureFingerprint.kt new file mode 100644 index 000000000..bce8877d8 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/fingerprints/MusicPlaybackControlsTimeBarOnMeasureFingerprint.kt @@ -0,0 +1,18 @@ +package app.revanced.patches.music.utils.sponsorblock.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +object MusicPlaybackControlsTimeBarOnMeasureFingerprint : MethodFingerprint( + returnType = "V", + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ), + customFingerprint = { methodDef, _ -> + methodDef.definingClass.endsWith("/MusicPlaybackControlsTimeBar;") + && methodDef.name == "onMeasure" + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/fingerprints/SeekbarOnDrawFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/fingerprints/SeekbarOnDrawFingerprint.kt new file mode 100644 index 000000000..dc082acd9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/fingerprints/SeekbarOnDrawFingerprint.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.music.utils.sponsorblock.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SeekbarOnDrawFingerprint : MethodFingerprint( + customFingerprint = { methodDef, _ -> methodDef.name == "onDraw" } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt new file mode 100644 index 000000000..9656cdc65 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/bytecode/patch/SponsorBlockBytecodePatch.kt @@ -0,0 +1,177 @@ +package app.revanced.patches.music.utils.sponsorblock.bytecode.patch + +import app.revanced.extensions.exception +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patches.music.utils.fingerprints.SeekBarConstructorFingerprint +import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch +import app.revanced.patches.music.utils.sponsorblock.bytecode.fingerprints.MusicPlaybackControlsTimeBarDrawFingerprint +import app.revanced.patches.music.utils.sponsorblock.bytecode.fingerprints.MusicPlaybackControlsTimeBarOnMeasureFingerprint +import app.revanced.patches.music.utils.sponsorblock.bytecode.fingerprints.SeekbarOnDrawFingerprint +import app.revanced.patches.music.utils.videoid.patch.VideoIdPatch +import app.revanced.patches.music.utils.videoinformation.patch.VideoInformationPatch +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction3rc +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +@DependsOn( + [ + SharedResourceIdPatch::class, + VideoIdPatch::class, + VideoInformationPatch::class + ] +) +class SponsorBlockBytecodePatch : BytecodePatch( + listOf( + MusicPlaybackControlsTimeBarDrawFingerprint, + MusicPlaybackControlsTimeBarOnMeasureFingerprint, + SeekBarConstructorFingerprint + ) +) { + override fun execute(context: BytecodeContext) { + + /** + * Hook the video time methods & Initialize the player controller + */ + VideoInformationPatch.apply { + videoTimeHook( + INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR, + "setVideoTime" + ) + onCreateHook( + INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR, + "initialize" + ) + } + + + /** + * Responsible for seekbar in fullscreen + */ + SeekBarConstructorFingerprint.result?.classDef?.let { classDef -> + SeekbarOnDrawFingerprint.also { + it.resolve( + context, + classDef + ) + }.result?.let { + it.mutableMethod.apply { + // Initialize seekbar method + addInstructions( + 0, """ + move-object/from16 v0, p0 + const-string v1, "${VideoInformationPatch.rectangleFieldName}" + invoke-static {v0, v1}, $INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarRect(Ljava/lang/Object;Ljava/lang/String;)V + """ + ) + + // Set seekbar thickness + for ((index, instruction) in implementation!!.instructions.withIndex()) { + if (instruction.opcode != Opcode.INVOKE_STATIC) continue + + val invokeInstruction = getInstruction(index) + if ((invokeInstruction.reference as MethodReference).name != "round") continue + + val insertIndex = index + 2 + + addInstruction( + insertIndex, + "invoke-static {v${invokeInstruction.registerC}}, $INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarThickness(I)V" + ) + break + } + + // Draw segment + for ((index, instruction) in implementation!!.instructions.withIndex()) { + if (instruction.opcode != Opcode.INVOKE_VIRTUAL_RANGE) continue + + val invokeInstruction = instruction as BuilderInstruction3rc + if ((invokeInstruction.reference as MethodReference).name != "restore") continue + + val drawSegmentInstructionInsertIndex = index - 1 + + val (canvasInstance, centerY) = + getInstruction( + drawSegmentInstructionInsertIndex + ).let { drawSegmentInstruction -> + drawSegmentInstruction.registerC to drawSegmentInstruction.registerE + } + + addInstruction( + drawSegmentInstructionInsertIndex, + "invoke-static {v$canvasInstance, v$centerY}, $INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V" + ) + break + } + } + } ?: throw SeekbarOnDrawFingerprint.exception + } ?: throw SeekBarConstructorFingerprint.exception + + + /** + * Responsible for seekbar in player + */ + MusicPlaybackControlsTimeBarOnMeasureFingerprint.result?.let { + it.mutableMethod.apply { + val rectangleIndex = it.scanResult.patternScanResult!!.startIndex + val rectangleReference = getInstruction(rectangleIndex).reference + rectangleFieldName = (rectangleReference as FieldReference).name + } + } ?: throw MusicPlaybackControlsTimeBarOnMeasureFingerprint.exception + + MusicPlaybackControlsTimeBarDrawFingerprint.result?.let { + it.mutableMethod.apply { + // Initialize seekbar method + addInstructions( + 1, """ + move-object/from16 v0, p0 + const-string v1, "$rectangleFieldName" + invoke-static {v0, v1}, $INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setSponsorBarRect(Ljava/lang/Object;Ljava/lang/String;)V + """ + ) + + // Draw segment + for ((index, instruction) in implementation!!.instructions.withIndex()) { + if (instruction.opcode != Opcode.INVOKE_VIRTUAL) continue + + val invokeInstruction = getInstruction(index) + if ((invokeInstruction.reference as MethodReference).name != "drawCircle") continue + + val (canvasInstance, centerY) = + getInstruction( + index + ).let { drawSegmentInstruction -> + drawSegmentInstruction.registerC to drawSegmentInstruction.registerE + } + + addInstruction( + index, + "invoke-static {v$canvasInstance, v$centerY}, $INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V" + ) + break + } + } + } ?: throw MusicPlaybackControlsTimeBarDrawFingerprint.exception + + /** + * Set current video id + */ + VideoIdPatch.injectCall("$INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR->setCurrentVideoId(Ljava/lang/String;)V") + } + + private companion object { + const val INTEGRATIONS_SEGMENT_PLAYBACK_CONTROLLER_CLASS_DESCRIPTOR = + "Lapp/revanced/music/sponsorblock/SegmentPlaybackController;" + + lateinit var rectangleFieldName: String + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/resource/patch/SponsorBlockPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/resource/patch/SponsorBlockPatch.kt new file mode 100644 index 000000000..1e22756f7 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/sponsorblock/resource/patch/SponsorBlockPatch.kt @@ -0,0 +1,66 @@ +package app.revanced.patches.music.utils.sponsorblock.resource.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.music.utils.annotations.MusicCompatibility +import app.revanced.patches.music.utils.settings.resource.patch.SettingsPatch +import app.revanced.patches.music.utils.sponsorblock.bytecode.patch.SponsorBlockBytecodePatch +import app.revanced.util.resources.MusicResourceHelper +import app.revanced.util.resources.MusicResourceHelper.hookPreference +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources + +@Patch +@Name("SponsorBlock") +@Description("Integrates SponsorBlock which allows skipping video segments such as sponsored content.") +@DependsOn( + [ + SettingsPatch::class, + SponsorBlockBytecodePatch::class + ] +) +@MusicCompatibility +class SponsorBlockPatch : ResourcePatch { + override fun execute(context: ResourceContext) { + + /** + * Copy preference + */ + arrayOf( + ResourceUtils.ResourceGroup( + "xml", + "sponsorblock_prefs.xml" + ) + ).forEach { resourceGroup -> + context.copyResources("music/sponsorblock", resourceGroup) + } + + /** + * Hook SponsorBlock preference + */ + context.hookPreference( + "revanced_sponsorblock_settings", + "com.google.android.apps.youtube.music.settings.fragment.AdvancedPrefsFragmentCompat" + ) + + val publicFile = context["res/values/public.xml"] + + publicFile.writeText( + publicFile.readText() + .replace( + "\"advanced_prefs_compat\"", + "\"sponsorblock_prefs\"" + ) + ) + + context["res/xml/sponsorblock_prefs.xml"].writeText( + context["res/xml/sponsorblock_prefs.xml"].readText() + .replace("\"com.google.android.apps.youtube.music\"", "\"" + MusicResourceHelper.targetPackage + "\"") + ) + + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt new file mode 100644 index 000000000..0df55cdba --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/PlayerControllerSetTimeReferenceFingerprint.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.music.utils.videoinformation.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +object PlayerControllerSetTimeReferenceFingerprint : MethodFingerprint( + returnType = "V", + opcodes = listOf( + Opcode.INVOKE_DIRECT_RANGE, + Opcode.IGET_OBJECT + ), + strings = listOf("Media progress reported outside media playback: ") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/PlayerInitFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/PlayerInitFingerprint.kt new file mode 100644 index 000000000..a28517523 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/PlayerInitFingerprint.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.music.utils.videoinformation.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object PlayerInitFingerprint : MethodFingerprint( + strings = listOf("playVideo called on player response with no videoStreamingData."), +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/SeekFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/SeekFingerprint.kt new file mode 100644 index 000000000..d1c0856ed --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/SeekFingerprint.kt @@ -0,0 +1,7 @@ +package app.revanced.patches.music.utils.videoinformation.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object SeekFingerprint : MethodFingerprint( + strings = listOf("Attempting to seek during an ad") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/VideoLengthFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/VideoLengthFingerprint.kt new file mode 100644 index 000000000..dc2a322f4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/fingerprints/VideoLengthFingerprint.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.music.utils.videoinformation.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +object VideoLengthFingerprint : MethodFingerprint( + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_WIDE, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/patch/VideoInformationPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/patch/VideoInformationPatch.kt new file mode 100644 index 000000000..21d4c1996 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/videoinformation/patch/VideoInformationPatch.kt @@ -0,0 +1,170 @@ +package app.revanced.patches.music.utils.videoinformation.patch + +import app.revanced.extensions.exception +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable +import app.revanced.patches.music.utils.annotations.MusicCompatibility +import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch +import app.revanced.patches.music.utils.videoinformation.fingerprints.PlayerControllerSetTimeReferenceFingerprint +import app.revanced.patches.music.utils.videoinformation.fingerprints.PlayerInitFingerprint +import app.revanced.patches.music.utils.fingerprints.SeekBarConstructorFingerprint +import app.revanced.patches.music.utils.videoinformation.fingerprints.SeekFingerprint +import app.revanced.patches.music.utils.videoinformation.fingerprints.VideoLengthFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference +import com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter +import com.android.tools.smali.dexlib2.util.MethodUtil + +@Name("Video information") +@Description("Hooks YouTube to get information about the current playing video.") +@DependsOn( + [ + SharedResourceIdPatch::class + ] +) +@MusicCompatibility +class VideoInformationPatch : BytecodePatch( + listOf( + PlayerControllerSetTimeReferenceFingerprint, + PlayerInitFingerprint, + SeekBarConstructorFingerprint, + ) +) { + override fun execute(context: BytecodeContext) { + PlayerInitFingerprint.result?.let { parentResult -> + playerInitMethod = + parentResult.mutableClass.methods.first { MethodUtil.isConstructor(it) } + + // hook the player controller for use through integrations + onCreateHook(INTEGRATIONS_CLASS_DESCRIPTOR, "initialize") + + SeekFingerprint.also { it.resolve(context, parentResult.classDef) }.result?.let { + it.mutableMethod.apply { + val seekHelperMethod = ImmutableMethod( + definingClass, + "seekTo", + listOf(ImmutableMethodParameter("J", annotations, "time")), + "Z", + AccessFlags.PUBLIC or AccessFlags.FINAL, + annotations, null, + MutableMethodImplementation(4) + ).toMutable() + + val seekSourceEnumType = parameterTypes[1].toString() + + seekHelperMethod.addInstructions( + 0, """ + sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType + invoke-virtual {p0, p1, p2, v0}, ${definingClass}->${name}(J$seekSourceEnumType)Z + move-result p1 + return p1 + """ + ) + + parentResult.mutableClass.methods.add(seekHelperMethod) + } + } ?: throw SeekFingerprint.exception + } ?: throw PlayerInitFingerprint.exception + + /** + * Set current video length + */ + SeekBarConstructorFingerprint.result?.classDef?.let { classDef -> + VideoLengthFingerprint.also { + it.resolve( + context, + classDef + ) + }.result?.let { + it.mutableMethod.apply { + val rectangleReference = getInstruction(implementation!!.instructions.count() - 3).reference + rectangleFieldName = (rectangleReference as FieldReference).name + + val videoLengthRegisterIndex = it.scanResult.patternScanResult!!.startIndex + 1 + val videoLengthRegister = getInstruction(videoLengthRegisterIndex).registerA + val dummyRegisterForLong = videoLengthRegister + 1 // required for long values since they are wide + + addInstruction( + videoLengthRegisterIndex + 1, + "invoke-static {v$videoLengthRegister, v$dummyRegisterForLong}, $INTEGRATIONS_CLASS_DESCRIPTOR->setVideoLength(J)V" + ) + } + } ?: throw VideoLengthFingerprint.exception + } ?: throw SeekBarConstructorFingerprint.exception + + /** + * Set the video time method + */ + PlayerControllerSetTimeReferenceFingerprint.result?.let { + timeMethod = context.toMethodWalker(it.method) + .nextMethod(it.scanResult.patternScanResult!!.startIndex, true) + .getMethod() as MutableMethod + } ?: throw PlayerControllerSetTimeReferenceFingerprint.exception + + /** + * Set current video time + */ + videoTimeHook(INTEGRATIONS_CLASS_DESCRIPTOR, "setVideoTime") + } + + companion object { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = "Lapp/revanced/music/patches/utils/VideoInformation;" + + private lateinit var playerInitMethod: MutableMethod + private var playerInitInsertIndex = 4 + + private lateinit var timeMethod: MutableMethod + private var timeInitInsertIndex = 2 + + lateinit var rectangleFieldName: String + + private fun MutableMethod.insert(insertIndex: Int, register: String, descriptor: String) = + addInstruction(insertIndex, "invoke-static { $register }, $descriptor") + + private fun MutableMethod.insertTimeHook(insertIndex: Int, descriptor: String) = + insert(insertIndex, "p1, p2", descriptor) + + /** + * Hook the player controller. Called when a video is opened or the current video is changed. + * + * Note: This hook is called very early and is called before the video id, video time, video length, + * and many other data fields are set. + * + * @param targetMethodClass The descriptor for the class to invoke when the player controller is created. + * @param targetMethodName The name of the static method to invoke when the player controller is created. + */ + internal fun onCreateHook(targetMethodClass: String, targetMethodName: String) = + playerInitMethod.insert( + playerInitInsertIndex++, + "v0", + "$targetMethodClass->$targetMethodName(Ljava/lang/Object;)V" + ) + + /** + * Hook the video time. + * The hook is usually called once per second. + * + * @param targetMethodClass The descriptor for the static method to invoke when the player controller is created. + * @param targetMethodName The name of the static method to invoke when the player controller is created. + */ + internal fun videoTimeHook(targetMethodClass: String, targetMethodName: String) = + timeMethod.insertTimeHook( + timeInitInsertIndex++, + "$targetMethodClass->$targetMethodName(J)V" + ) + } +} \ No newline at end of file diff --git a/src/main/resources/music/settings/host/values/strings.xml b/src/main/resources/music/settings/host/values/strings.xml index e00dea332..52534ed89 100644 --- a/src/main/resources/music/settings/host/values/strings.xml +++ b/src/main/resources/music/settings/host/values/strings.xml @@ -113,4 +113,57 @@ Changing default Wi-Fi quality to: Trick the YouTube Music version to v4.27.53 for Canadian users. Spoof app version + + Enable SponsorBlock + SponsorBlock is a crowd-sourced system for skipping annoying parts of YouTube videos. + + Show a toast when skipping automatically + Toast shown when a segment is automatically skipped. + + Change API URL + The address SponsorBlock uses to make calls to the server. Do not change this unless you know what you\'re doing + API URL reset + API URL is invalid + API URL changed + + Change segment behavior + Sponsor + Paid promotion, paid referrals and direct advertisements. Not for self-promotion or free shout-outs to causes/creators/websites/products they like + Unpaid / Self Promotion + Similar to \'Sponsor\' except for unpaid or self promotion. Includes sections about merchandise, donations, or information about who they collaborated with + Interaction Reminder (Subscribe) + A short reminder to like, subscribe or follow them in the middle of content. If it is long or about something specific, it should instead be under self promotion + Intermission / Intro Animation + An interval without actual content. Could be a pause, static frame, or repeating animation. Does not include transitions containing information + Endcards / Credits + Credits or when the YouTube endcards appear. Not for conclusions with information + Preview / Recap + Collection of clips that show what is coming up or what happened in the video or in other videos of a series, where all information is repeated elsewhere + Filler Tangent / Jokes + Tangential scenes added only for filler or humor that are not required to understand the main content of the video. Does not include segments providing context or background details + Music: Non-Music Section + Only for use in music videos. Sections of music videos without music, that aren\'t already covered by another category + + Skipped sponsor + Skipped self promotion + Skipped annoying reminder + Skipped intro + Skipped intermission + Skipped intermission + Skipped outro + Skipped preview + Skipped preview + Skipped recap + Skipped filler + Skipped a non-music section + Skipped multiple segments + Skip automatically + Disable + + Color: + Color changed + Color reset + Invalid color code + Reset color + Data is provided by the SponsorBlock API. Tap here to learn more and see downloads for other platforms diff --git a/src/main/resources/music/settings/values-v21/strings.xml b/src/main/resources/music/settings/values-v21/strings.xml index 495a4e6ab..2ccf3a6b3 100644 --- a/src/main/resources/music/settings/values-v21/strings.xml +++ b/src/main/resources/music/settings/values-v21/strings.xml @@ -4,4 +4,7 @@ ReVanced Extended @string/revanced_ryd_settings_title Return YouTube Dislike + SponsorBlock + @string/revanced_ryd_about + sponsor.ajay.app diff --git a/src/main/resources/music/sponsorblock/xml/sponsorblock_prefs.xml b/src/main/resources/music/sponsorblock/xml/sponsorblock_prefs.xml new file mode 100644 index 000000000..80b3abf37 --- /dev/null +++ b/src/main/resources/music/sponsorblock/xml/sponsorblock_prefs.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file