This commit is contained in:
inotia00 2023-09-13 12:54:57 +09:00
parent 31df1c67a6
commit dec7dbe37b
15 changed files with 601 additions and 0 deletions

View File

@ -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) }
)

View File

@ -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")

View File

@ -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"
}
)

View File

@ -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"
}
)

View File

@ -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" }
)

View File

@ -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<Instruction35c>(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<FiveRegisterInstruction>(
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<ReferenceInstruction>(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<Instruction35c>(index)
if ((invokeInstruction.reference as MethodReference).name != "drawCircle") continue
val (canvasInstance, centerY) =
getInstruction<FiveRegisterInstruction>(
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
}
}

View File

@ -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 + "\"")
)
}
}

View File

@ -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: ")
)

View File

@ -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."),
)

View File

@ -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")
)

View File

@ -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
)
)

View File

@ -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<ReferenceInstruction>(implementation!!.instructions.count() - 3).reference
rectangleFieldName = (rectangleReference as FieldReference).name
val videoLengthRegisterIndex = it.scanResult.patternScanResult!!.startIndex + 1
val videoLengthRegister = getInstruction<OneRegisterInstruction>(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"
)
}
}

View File

@ -113,4 +113,57 @@
<string name="revanced_save_video_quality_wifi">Changing default Wi-Fi quality to:</string>
<string name="revanced_spoof_app_version_summary">Trick the YouTube Music version to v4.27.53 for Canadian users.</string>
<string name="revanced_spoof_app_version_title">Spoof app version</string>
<string name="sb_enabled">Enable SponsorBlock</string>
<string name="sb_enabled_sum">SponsorBlock is a crowd-sourced system for skipping annoying parts of YouTube videos.</string>
<string name="sb_toast_on_skip">Show a toast when skipping automatically</string>
<string name="sb_toast_on_skip_sum">Toast shown when a segment is automatically skipped.</string>
<string name="sb_api_url">Change API URL</string>
<string name="sb_api_url_sum">The address SponsorBlock uses to make calls to the server. Do not change this unless you know what you\'re doing</string>
<string name="sb_api_url_reset">API URL reset</string>
<string name="sb_api_url_invalid">API URL is invalid</string>
<string name="sb_api_url_changed">API URL changed</string>
<string name="sb_diff_segments">Change segment behavior</string>
<string name="sb_segments_sponsor">Sponsor</string>
<string name="sb_segments_sponsor_sum">Paid promotion, paid referrals and direct advertisements. Not for self-promotion or free shout-outs to causes/creators/websites/products they like</string>
<string name="sb_segments_selfpromo">Unpaid/Self Promotion</string>
<string name="sb_segments_selfpromo_sum">Similar to \'Sponsor\' except for unpaid or self promotion. Includes sections about merchandise, donations, or information about who they collaborated with</string>
<string name="sb_segments_interaction">Interaction Reminder (Subscribe)</string>
<string name="sb_segments_interaction_sum">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</string>
<string name="sb_segments_intro">Intermission/Intro Animation</string>
<string name="sb_segments_intro_sum">An interval without actual content. Could be a pause, static frame, or repeating animation. Does not include transitions containing information</string>
<string name="sb_segments_outro">Endcards/Credits</string>
<string name="sb_segments_outro_sum">Credits or when the YouTube endcards appear. Not for conclusions with information</string>
<string name="sb_segments_preview">Preview/Recap</string>
<string name="sb_segments_preview_sum">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</string>
<string name="sb_segments_filler">Filler Tangent/Jokes</string>
<string name="sb_segments_filler_sum">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</string>
<string name="sb_segments_nomusic">Music: Non-Music Section</string>
<string name="sb_segments_nomusic_sum">Only for use in music videos. Sections of music videos without music, that aren\'t already covered by another category</string>
<string name="sb_skipped_sponsor">Skipped sponsor</string>
<string name="sb_skipped_selfpromo">Skipped self promotion</string>
<string name="sb_skipped_interaction">Skipped annoying reminder</string>
<string name="sb_skipped_intro_beginning">Skipped intro</string>
<string name="sb_skipped_intro_middle">Skipped intermission</string>
<string name="sb_skipped_intro_end">Skipped intermission</string>
<string name="sb_skipped_outro">Skipped outro</string>
<string name="sb_skipped_preview_beginning">Skipped preview</string>
<string name="sb_skipped_preview_middle">Skipped preview</string>
<string name="sb_skipped_preview_end">Skipped recap</string>
<string name="sb_skipped_filler">Skipped filler</string>
<string name="sb_skipped_nomusic">Skipped a non-music section</string>
<string name="sb_skipped_multiple_segments">Skipped multiple segments</string>
<string name="sb_skip_automatically">Skip automatically</string>
<string name="sb_skip_ignore">Disable</string>
<string name="sb_color_dot_label">Color:</string>
<string name="sb_color_changed">Color changed</string>
<string name="sb_color_reset">Color reset</string>
<string name="sb_color_invalid">Invalid color code</string>
<string name="sb_reset_color">Reset color</string>
<string name="sb_about_api_sum">Data is provided by the SponsorBlock API. Tap here to learn more and see downloads for other platforms</string>
</resources>

View File

@ -4,4 +4,7 @@
<string name="revanced_extended_settings_title">ReVanced Extended</string>
<string name="revanced_ryd_enabled_title">@string/revanced_ryd_settings_title</string>
<string name="revanced_ryd_settings_title">Return YouTube Dislike</string>
<string name="revanced_sponsorblock_settings_title">SponsorBlock</string>
<string name="sb_about">@string/revanced_ryd_about</string>
<string name="sb_about_api">sponsor.ajay.app</string>
</resources>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:yt="http://schemas.android.com/apk/res-auto">
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/sb_enabled" android:key="sb_enabled" android:summary="@string/sb_enabled_sum" android:defaultValue="true" />
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/sb_toast_on_skip" android:key="sb_toast_on_skip" android:summary="@string/sb_toast_on_skip_sum" android:dependency="sb_enabled" android:defaultValue="true" />
<Preference android:title="@string/sb_api_url" android:key="sb_api_url" android:summary="@string/sb_api_url_sum" android:dependency="sb_enabled">
<intent android:targetPackage="com.google.android.apps.youtube.music" android:data="sb_api_url" android:targetClass="com.google.android.libraries.strictmode.penalties.notification.FullStackTraceActivity" />
</Preference>
<com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat android:title="@string/sb_diff_segments" android:key="segments">
<Preference android:title="@string/sb_segments_sponsor" android:key="sb_segments_sponsor" android:summary="@string/sb_segments_sponsor_sum">
<intent android:targetPackage="com.google.android.apps.youtube.music" android:data="sb_segments_sponsor" android:targetClass="com.google.android.libraries.strictmode.penalties.notification.FullStackTraceActivity" />
</Preference>
<Preference android:title="@string/sb_segments_selfpromo" android:key="sb_segments_selfpromo" android:summary="@string/sb_segments_selfpromo_sum">
<intent android:targetPackage="com.google.android.apps.youtube.music" android:data="sb_segments_selfpromo" android:targetClass="com.google.android.libraries.strictmode.penalties.notification.FullStackTraceActivity" />
</Preference>
<Preference android:title="@string/sb_segments_interaction" android:key="sb_segments_interaction" android:summary="@string/sb_segments_interaction_sum">
<intent android:targetPackage="com.google.android.apps.youtube.music" android:data="sb_segments_interaction" android:targetClass="com.google.android.libraries.strictmode.penalties.notification.FullStackTraceActivity" />
</Preference>
<Preference android:title="@string/sb_segments_intro" android:key="sb_segments_intro" android:summary="@string/sb_segments_intro_sum">
<intent android:targetPackage="com.google.android.apps.youtube.music" android:data="sb_segments_intro" android:targetClass="com.google.android.libraries.strictmode.penalties.notification.FullStackTraceActivity" />
</Preference>
<Preference android:title="@string/sb_segments_outro" android:key="sb_segments_outro" android:summary="@string/sb_segments_outro_sum">
<intent android:targetPackage="com.google.android.apps.youtube.music" android:data="sb_segments_outro" android:targetClass="com.google.android.libraries.strictmode.penalties.notification.FullStackTraceActivity" />
</Preference>
<Preference android:title="@string/sb_segments_preview" android:key="sb_segments_preview" android:summary="@string/sb_segments_preview_sum">
<intent android:targetPackage="com.google.android.apps.youtube.music" android:data="sb_segments_preview" android:targetClass="com.google.android.libraries.strictmode.penalties.notification.FullStackTraceActivity" />
</Preference>
<Preference android:title="@string/sb_segments_filler" android:key="sb_segments_filler" android:summary="@string/sb_segments_filler_sum">
<intent android:targetPackage="com.google.android.apps.youtube.music" android:data="sb_segments_filler" android:targetClass="com.google.android.libraries.strictmode.penalties.notification.FullStackTraceActivity" />
</Preference>
<Preference android:title="@string/sb_segments_nomusic" android:key="sb_segments_music_offtopic" android:summary="@string/sb_segments_nomusic_sum">
<intent android:targetPackage="com.google.android.apps.youtube.music" android:data="sb_segments_music_offtopic" android:targetClass="com.google.android.libraries.strictmode.penalties.notification.FullStackTraceActivity" />
</Preference>
</com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat>
<com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat android:title="@string/sb_about" android:key="about">
<Preference android:title="@string/sb_about_api" android:key="sb_about_api" android:summary="@string/sb_about_api_sum">
<intent android:action="android.intent.action.VIEW" android:data="https://sponsor.ajay.app" />
</Preference>
</com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat>
</PreferenceScreen>