feat: settings patch framework (#266)

This commit is contained in:
oSumAtrIX
2022-08-22 01:59:43 +02:00
committed by GitHub
parent 059913c239
commit 8af8cdbfd6
62 changed files with 1219 additions and 728 deletions

View File

@ -23,6 +23,9 @@ import app.revanced.patches.youtube.ad.general.bytecode.extensions.MethodExtensi
import app.revanced.patches.youtube.ad.general.bytecode.utils.MethodUtils.createMutableMethod
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.mapping.patch.ResourceIdMappingProviderResourcePatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.MutableMethodImplementation
import org.jf.dexlib2.builder.instruction.*
@ -38,7 +41,7 @@ import org.jf.dexlib2.iface.reference.StringReference
import org.jf.dexlib2.immutable.reference.ImmutableMethodReference
@Patch
@DependsOn([ResourceIdMappingProviderResourcePatch::class, IntegrationsPatch::class])
@DependsOn([ResourceIdMappingProviderResourcePatch::class, IntegrationsPatch::class, SettingsPatch::class])
@Name("general-ads")
@Description("Removes general ads.")
@GeneralAdsCompatibility
@ -68,6 +71,135 @@ class GeneralBytecodeAdsPatch : BytecodePatch() {
)
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.ADS.addPreferences(
SwitchPreference(
"revanced_home_ads_enabled",
StringResource("revanced_home_ads_enabled_title", "Hide home ads"),
true,
StringResource("revanced_home_ads_enabled_summary_on", "Home ads are shown"),
StringResource("revanced_home_ads_enabled_summary_off", "Home ads are hidden")
),
SwitchPreference(
"revanced_adremover_ad_removal",
StringResource("revanced_adremover_ad_removal_enabled_title", "Remove general ads"),
true,
StringResource("revanced_adremover_ad_removal_enabled_summary_on", "General ads are hidden"),
StringResource("revanced_adremover_ad_removal_enabled_summary_off", "General ads are shown")
),
SwitchPreference(
"revanced_adremover_merchandise",
StringResource("revanced_adremover_merchandise_enabled_title", "Remove merchandise banners"),
true,
StringResource("revanced_adremover_merchandise_enabled_summary_on", "Merchandise banners are hidden"),
StringResource("revanced_adremover_merchandise_enabled_summary_off", "Merchandise banners are shown")
),
SwitchPreference(
"revanced_adremover_community_posts_removal",
StringResource("revanced_adremover_community_posts_enabled_title", "Remove community posts"),
true,
StringResource("revanced_adremover_community_posts_enabled_summary_on", "Community posts are hidden"),
StringResource("revanced_adremover_community_posts_enabled_summary_off", "Community posts are shown")
),
SwitchPreference(
"revanced_adremover_compact_banner_removal",
StringResource("revanced_adremover_compact_banner_enabled_title", "Remove compact banners"),
true,
StringResource("revanced_adremover_compact_banner_enabled_summary_on", "Compact banners are hidden"),
StringResource("revanced_adremover_compact_banner_enabled_summary_off", "Compact banners are shown")
),
SwitchPreference(
"revanced_adremover_comments_removal",
StringResource("revanced_adremover_comments_enabled_title", "Remove comments section"),
false,
StringResource("revanced_adremover_comments_enabled_summary_on", "Comment section is hidden"),
StringResource("revanced_adremover_comments_enabled_summary_off", "Comment section is shown")
),
SwitchPreference(
"revanced_adremover_movie",
StringResource("revanced_adremover_movie_enabled_title", "Remove movies section"),
true,
StringResource("revanced_adremover_movie_enabled_summary_on", "Movies section is hidden"),
StringResource("revanced_adremover_movie_enabled_summary_off", "Movies section is shown")
),
SwitchPreference(
"revanced_adremover_feed_survey",
StringResource("revanced_adremover_feed_survey_enabled_title", "Remove feed surveys"),
true,
StringResource("revanced_adremover_feed_survey_enabled_summary_on", "Feed surveys are hidden"),
StringResource("revanced_adremover_feed_survey_enabled_summary_off", "Feed surveys are shown")
),
SwitchPreference(
"revanced_adremover_shorts_shelf",
StringResource("revanced_adremover_shorts_shelf_enabled_title", "Remove shorts shelf"),
true,
StringResource("revanced_adremover_shorts_shelf_enabled_summary_on", "Shorts shelves are hidden"),
StringResource("revanced_adremover_shorts_shelf_enabled_summary_off", "Shorts shelves are shown")
),
SwitchPreference(
"revanced_adremover_community_guidelines",
StringResource("revanced_adremover_community_guidelines_enabled_title", "Remove community guidelines"),
true,
StringResource("revanced_adremover_community_guidelines_enabled_summary_on", "Community guidelines are hidden"),
StringResource("revanced_adremover_community_guidelines_enabled_summary_off", "Community guidelines are shown")
),
SwitchPreference(
"revanced_adremover_emergency_box_removal",
StringResource("revanced_adremover_emergency_box_enabled_title", "Remove emergency boxes"),
true,
StringResource("revanced_adremover_emergency_box_enabled_summary_on", "Emergency boxes are hidden"),
StringResource("revanced_adremover_emergency_box_enabled_summary_off", "Emergency boxes are shown")
),
SwitchPreference(
"revanced_adremover_info_panel",
StringResource("revanced_adremover_info_panel_enabled_title", "Remove info panels"),
true,
StringResource("revanced_adremover_info_panel_enabled_summary_on", "Merchandise banners are hidden"),
StringResource("revanced_adremover_info_panel_enabled_summary_off", "Merchandise banners are shown")
),
SwitchPreference(
"revanced_adremover_medical_panel",
StringResource("revanced_adremover_medical_panel_enabled_title", "Remove medical panels"),
true,
StringResource("revanced_adremover_medical_panel_enabled_summary_on", "Medical panels are hidden"),
StringResource("revanced_adremover_medical_panel_enabled_summary_off", "Medical panels are shown")
),
SwitchPreference(
"revanced_adremover_paid_content",
StringResource("revanced_adremover_paid_content_enabled_title", "Remove paid content"),
true,
StringResource("revanced_adremover_paid_content_enabled_summary_on", "Paid content is hidden"),
StringResource("revanced_adremover_paid_content_enabled_summary_off", "Paid content is shown")
),
SwitchPreference(
"revanced_adremover_suggested",
StringResource("revanced_adremover_suggested_enabled_title", "Remove personal suggestions"),
true,
StringResource("revanced_adremover_suggested_enabled_summary_on", "Personal suggestions are hidden"),
StringResource("revanced_adremover_suggested_enabled_summary_off", "Personal suggestions are shown")
),
SwitchPreference(
"revanced_adremover_hide_suggestions",
StringResource("revanced_adremover_hide_suggestions_enabled_title", "Hide suggestions"),
true,
StringResource("revanced_adremover_hide_suggestions_enabled_summary_on", "Suggestions are hidden"),
StringResource("revanced_adremover_hide_suggestions_enabled_summary_off", "Suggestions are shown")
),
SwitchPreference(
"revanced_adremover_hide_latest_posts",
StringResource("revanced_adremover_hide_latest_posts_enabled_title", "Hide latest posts"),
true,
StringResource("revanced_adremover_hide_latest_posts_enabled_summary_on", "Latest posts are hidden"),
StringResource("revanced_adremover_hide_latest_posts_enabled_summary_off", "Latest posts are shown")
),
SwitchPreference(
"revanced_adremover_hide_channel_guidelines",
StringResource("revanced_adremover_hide_channel_guidelines_enabled_title", "Hide channel guidelines"),
true,
StringResource("revanced_adremover_hide_channel_guidelines_enabled_summary_on", "Channel guidelines are hidden"),
StringResource("revanced_adremover_hide_channel_guidelines_enabled_summary_off", "Channel guidelines are shown")
),
)
// iterating through all classes is expensive
for (classDef in data.classes) {
var mutableClass: MutableClass? = null

View File

@ -16,10 +16,13 @@ import app.revanced.patches.youtube.ad.infocardsuggestions.annotations.HideInfoc
import app.revanced.patches.youtube.ad.infocardsuggestions.fingerprints.HideInfocardSuggestionsFingerprint
import app.revanced.patches.youtube.ad.infocardsuggestions.fingerprints.HideInfocardSuggestionsParentFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import org.jf.dexlib2.builder.instruction.BuilderInstruction35c
@Patch
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Name("hide-infocard-suggestions")
@Description("Hides infocards in videos.")
@HideInfocardSuggestionsCompatibility
@ -30,6 +33,16 @@ class HideInfocardSuggestionsPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.ADS.addPreferences(
SwitchPreference(
"revanced_info_cards_enabled",
StringResource("revanced_info_cards_enabled_title", "Show info-cards"),
false,
StringResource("revanced_info_cards_enabled_summary_on", "Info-cards are shown"),
StringResource("revanced_info_cards_enabled_summary_off", "Info-cards are hidden")
)
)
val parentResult = HideInfocardSuggestionsParentFingerprint.result
?: return PatchResultError("Parent fingerprint not resolved!")

View File

@ -15,9 +15,12 @@ import app.revanced.patches.youtube.ad.video.annotations.VideoAdsCompatibility
import app.revanced.patches.youtube.ad.video.fingerprints.ShowVideoAdsConstructorFingerprint
import app.revanced.patches.youtube.ad.video.fingerprints.ShowVideoAdsFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
@Patch
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Name("video-ads")
@Description("Removes ads in the video player.")
@VideoAdsCompatibility
@ -28,7 +31,19 @@ class VideoAdsPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
ShowVideoAdsFingerprint.resolve(data, ShowVideoAdsConstructorFingerprint.result!!.classDef)
SettingsPatch.PreferenceScreen.ADS.addPreferences(
SwitchPreference(
"revanced_video_ads_enabled",
StringResource("revanced_video_ads_enabled_title", "Hide video ads"),
true,
StringResource("revanced_video_ads_enabled_summary_on", "Video ads are hidden"),
StringResource("revanced_video_ads_enabled_summary_off", "Video ads are shown")
)
)
ShowVideoAdsFingerprint.resolve(
data, ShowVideoAdsConstructorFingerprint.result!!.classDef
)
// Override the parameter by calling shouldShowAds and setting the parameter to the result
ShowVideoAdsFingerprint.result!!.mutableMethod.addInstructions(
@ -40,4 +55,4 @@ class VideoAdsPatch : BytecodePatch(
return PatchResultSuccess()
}
}
}

View File

@ -15,6 +15,9 @@ import app.revanced.patches.youtube.interaction.seekbar.annotation.SeekbarTappin
import app.revanced.patches.youtube.interaction.seekbar.fingerprints.SeekbarTappingFingerprint
import app.revanced.patches.youtube.interaction.seekbar.fingerprints.SeekbarTappingParentFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.builder.instruction.BuilderInstruction21t
import org.jf.dexlib2.iface.Method
@ -22,7 +25,7 @@ import org.jf.dexlib2.iface.instruction.formats.Instruction11n
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
@Patch
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Name("seekbar-tapping")
@Description("Enables tap-to-seek on the seekbar of the video player.")
@SeekbarTappingCompatibility
@ -33,6 +36,16 @@ class EnableSeekbarTappingPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.INTERACTIONS.addPreferences(
SwitchPreference(
"revanced_enable_tap_seeking",
StringResource("revanced_seekbar_tapping_enabled_title", "Enable seekbar tapping"),
true,
StringResource("revanced_seekbar_tapping_summary_on", "Seekbar tapping is enabled"),
StringResource("revanced_seekbar_tapping_summary_off", "Seekbar tapping is disabled")
)
)
var result = SeekbarTappingParentFingerprint.result!!
val tapSeekMethods = mutableMapOf<String, Method>()
@ -42,7 +55,7 @@ class EnableSeekbarTappingPatch : BytecodePatch(
if (it.implementation == null) continue
val instructions = it.implementation!!.instructions
// here we make sure we actually find the method because it has more then 7 instructions
// here we make sure we actually find the method because it has more than 7 instructions
if (instructions.count() < 7) continue
// we know that the 7th instruction has the opcode CONST_4

View File

@ -65,5 +65,4 @@ class SwipeControlsBytecodePatch : BytecodePatch(
}
return PatchResultSuccess()
}
}
}

View File

@ -6,14 +6,91 @@ import app.revanced.patcher.annotation.Version
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.DependsOn
import app.revanced.patcher.patch.impl.ResourcePatch
import app.revanced.patches.youtube.interaction.swipecontrols.annotation.SwipeControlsCompatibility
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.*
@Name("swipe-controls-resource-patch")
@DependsOn([SettingsPatch::class])
@SwipeControlsCompatibility
@Version("0.0.1")
class SwipeControlsResourcePatch : ResourcePatch() {
override fun execute(data: ResourceData): PatchResult {
SettingsPatch.PreferenceScreen.INTERACTIONS.addPreferences(
PreferenceScreen(
"revanced_swipe_controls", StringResource("revanced_swipe_controls_title", "Swipe controls"), listOf(
SwitchPreference(
"revanced_enable_swipe_brightness",
StringResource("revanced_swipe_brightness_enabled_title", "Enable brightness gesture"),
true,
StringResource("revanced_swipe_brightness_summary_on", "Brightness swipe is enabled"),
StringResource("revanced_swipe_brightness_summary_off", "Brightness swipe is disabled")
),
SwitchPreference(
"revanced_enable_swipe_volume",
StringResource("revanced_swipe_volume_enabled_title", "Enable volume gesture"),
true,
StringResource("revanced_swipe_volume_summary_on", "Volume swipe is enabled"),
StringResource("revanced_swipe_volume_summary_off", "Volume swipe is disabled")
),
SwitchPreference(
"revanced_enable_press_to_swipe",
StringResource("revanced_press_to_swipe_enabled_title", "Enable press-to-swipe gesture"),
false,
StringResource("revanced_press_to_swipe_summary_on", "Press-to-swipe is enabled"),
StringResource("revanced_press_to_swipe_summary_off", "Press-to-swipe is disabled")
),
SwitchPreference(
"revanced_enable_swipe_haptic_feedback",
StringResource("revanced_swipe_haptic_feedback_enabled_title", "Enable haptic feedback"),
true,
StringResource("revanced_swipe_haptic_feedback_summary_on", "Haptic feedback is enabled"),
StringResource("revanced_swipe_haptic_feedback_summary_off", "Haptic feedback is disabled")
),
TextPreference(
"revanced_swipe_overlay_timeout",
StringResource("revanced_swipe_overlay_timeout_title", "Swipe overlay timeout"),
InputType.NUMBER,
"500",
StringResource(
"revanced_swipe_overlay_timeout_summary",
"The amount of milliseconds the overlay is visible"
)
),
TextPreference(
"revanced_swipe_overlay_text_size",
StringResource("revanced_swipe_overlay_text_size_title", "Swipe overlay text size"),
InputType.NUMBER,
"22",
StringResource("revanced_swipe_overlay_text_size_summary", "The text size for swipe overlay")
),
TextPreference(
"revanced_swipe_overlay_background_alpha",
StringResource("revanced_swipe_overlay_background_alpha_title", "Swipe background visibility"),
InputType.NUMBER,
"127",
StringResource(
"revanced_swipe_overlay_background_alpha_summary",
"The visibility of swipe overlay background"
)
),
TextPreference(
"revanced_swipe_magnitude_threshold",
StringResource("revanced_swipe_magnitude_threshold_title", "Swipe magnitude threshold"),
InputType.NUMBER,
"30",
StringResource(
"revanced_swipe_magnitude_threshold_summary",
"The amount of threshold for swipe to occur"
)
)
),
StringResource("revanced_swipe_controls_summary","Control volume and brightness")
)
)
val resourcesDir = "swipecontrols"
data.injectResources(

View File

@ -14,10 +14,9 @@ import org.jf.dexlib2.Opcode
@MatchingMethod(
"LWillAutonavInformer;", "k"
)
@FuzzyPatternScanMethod(2)
@AutoplayButtonCompatibility
@Version("0.0.1")
object AutonavInformerFingerprint : MethodFingerprint(
object AutoNavInformerFingerprint : MethodFingerprint(
"Z",
AccessFlags.PUBLIC or AccessFlags.FINAL,
null,
@ -31,4 +30,4 @@ object AutonavInformerFingerprint : MethodFingerprint(
),
null,
{ it.definingClass.endsWith("WillAutonavInformer;") }
)
)

View File

@ -2,31 +2,19 @@ package app.revanced.patches.youtube.layout.autoplaybutton.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.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.autoplaybutton.annotations.AutoplayButtonCompatibility
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
@Name("layout-constructor-fingerprint")
@MatchingMethod(
"LYouTubeControlsOverlay;", "F"
)
@FuzzyPatternScanMethod(2)
@AutoplayButtonCompatibility
@Version("0.0.1")
object LayoutConstructorFingerprint : MethodFingerprint(
"V",
AccessFlags.PUBLIC or AccessFlags.FINAL,
null,
listOf(
Opcode.CONST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
),
listOf("1.0x")
)
null, null, null, null, listOf("1.0x"),
{ methodDef ->
methodDef.definingClass.endsWith("YouTubeControlsOverlay;")
}
)

View File

@ -1,88 +0,0 @@
package app.revanced.patches.youtube.layout.autoplaybutton.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.extensions.removeInstruction
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.patches.youtube.layout.autoplaybutton.annotations.AutoplayButtonCompatibility
import app.revanced.patches.youtube.layout.autoplaybutton.fingerprints.AutonavInformerFingerprint
import app.revanced.patches.youtube.layout.autoplaybutton.fingerprints.LayoutConstructorFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.mapping.patch.ResourceIdMappingProviderResourcePatch
import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
@Patch
@DependsOn([ResourceIdMappingProviderResourcePatch::class, IntegrationsPatch::class])
@Name("hide-autoplay-button")
@Description("Hides the autoplay button in the video player.")
@AutoplayButtonCompatibility
@Version("0.0.1")
class HideAutoplayButton : BytecodePatch(
listOf(
LayoutConstructorFingerprint, AutonavInformerFingerprint
)
) {
override fun execute(data: BytecodeData): PatchResult {
val layoutGenMethod = LayoutConstructorFingerprint.result!!.mutableMethod
val autonavToggle =
ResourceIdMappingProviderResourcePatch.resourceMappings.single { it.type == "id" && it.name == "autonav_toggle" }
val autonavPreviewStub =
ResourceIdMappingProviderResourcePatch.resourceMappings.single { it.type == "id" && it.name == "autonav_preview_stub" }
val instructions = layoutGenMethod.implementation!!.instructions
val autonavToggleConstIndex =
instructions.indexOfFirst { (it as? WideLiteralInstruction)?.wideLiteral == autonavToggle.id } + 4
val autonavPreviewStubConstIndex =
instructions.indexOfFirst { (it as? WideLiteralInstruction)?.wideLiteral == autonavPreviewStub.id } + 4
injectIfBranch(layoutGenMethod, autonavToggleConstIndex)
injectIfBranch(layoutGenMethod, autonavPreviewStubConstIndex)
val autonavInformerMethod = AutonavInformerFingerprint.result!!.mutableMethod
//force disable autoplay since it's hard to do without the button
autonavInformerMethod.addInstructions(
0, """
invoke-static {}, Lapp/revanced/integrations/patches/HideAutoplayButtonPatch;->isButtonShown()Z
move-result v0
if-nez v0, :hidden
const/4 v0, 0x0
return v0
:hidden
nop
"""
)
return PatchResultSuccess()
}
private fun injectIfBranch(method: MutableMethod, index: Int) {
val instructions = method.implementation!!.instructions
val insn = (instructions.get(index) as? Instruction35c)!!
val methodToCall = insn.reference.toString()
//remove the invoke-virtual because we want to put it in an if-statement
method.removeInstruction(index)
method.addInstructions(
index, """
invoke-static {}, Lapp/revanced/integrations/patches/HideAutoplayButtonPatch;->isButtonShown()Z
move-result v11
if-eqz v11, :hidebutton
invoke-virtual {v${insn.registerC}, v${insn.registerD}, v${insn.registerE}}, $methodToCall
:hidebutton
nop
"""
)
}
}

View File

@ -0,0 +1,92 @@
package app.revanced.patches.youtube.layout.autoplaybutton.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.extensions.addInstructions
import app.revanced.patcher.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.smali.ExternalLabel
import app.revanced.patches.youtube.layout.autoplaybutton.annotations.AutoplayButtonCompatibility
import app.revanced.patches.youtube.layout.autoplaybutton.fingerprints.AutoNavInformerFingerprint
import app.revanced.patches.youtube.layout.autoplaybutton.fingerprints.LayoutConstructorFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.mapping.patch.ResourceIdMappingProviderResourcePatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import org.jf.dexlib2.iface.instruction.Instruction
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
import org.jf.dexlib2.iface.reference.MethodReference
@Patch
@DependsOn([IntegrationsPatch::class, SettingsPatch::class, ResourceIdMappingProviderResourcePatch::class])
@Name("hide-autoplay-button")
@Description("Hides the autoplay button in the video player.")
@AutoplayButtonCompatibility
@Version("0.0.1")
class HideAutoplayButtonPatch : BytecodePatch(
listOf(
LayoutConstructorFingerprint, AutoNavInformerFingerprint
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
SwitchPreference(
"revanced_autoplay_button_enabled",
StringResource("revanced_autoplay_button_enabled_title", "Show autoplay button"),
false,
StringResource("revanced_autoplay_button_summary_on", "Autoplay button is shown"),
StringResource("revanced_autoplay_button_summary_off", "Autoplay button is hidden")
)
)
val autoNavInformerMethod = AutoNavInformerFingerprint.result!!.mutableMethod
val layoutGenMethodResult = LayoutConstructorFingerprint.result!!
val layoutGenMethod = layoutGenMethodResult.mutableMethod
val layoutGenMethodInstructions = layoutGenMethod.implementation!!.instructions
// resolve the offsets such as ...
val autoNavPreviewStubId = ResourceIdMappingProviderResourcePatch.resourceMappings.single {
it.name == "autonav_preview_stub"
}.id
// where to insert the branch instructions and ...
val insertIndex = layoutGenMethodInstructions.indexOfFirst {
(it as? WideLiteralInstruction)?.wideLiteral == autoNavPreviewStubId
}
// where to branch away
val branchIndex = layoutGenMethodInstructions.subList(insertIndex + 1, layoutGenMethodInstructions.size - 1).indexOfFirst {
((it as? ReferenceInstruction)?.reference as? MethodReference)?.name == "addOnLayoutChangeListener"
} + 2
val jumpInstruction = layoutGenMethodInstructions[insertIndex + branchIndex] as Instruction
layoutGenMethod.addInstructions(
insertIndex, """
invoke-static {}, Lapp/revanced/integrations/patches/HideAutoplayButtonPatch;->isButtonShown()Z
move-result v11
if-eqz v11, :hidden
""", listOf(ExternalLabel("hidden", jumpInstruction))
)
//force disable autoplay since it's hard to do without the button
autoNavInformerMethod.addInstructions(
0, """
invoke-static {}, Lapp/revanced/integrations/patches/HideAutoplayButtonPatch;->isButtonShown()Z
move-result v0
if-nez v0, :hidden
const/4 v0, 0x0
return v0
:hidden
nop
"""
)
return PatchResultSuccess()
}
}

View File

@ -12,15 +12,28 @@ import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patches.youtube.layout.castbutton.annotations.CastButtonCompatibility
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
@Patch
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Name("hide-cast-button")
@Description("Hides the cast button in the video player.")
@CastButtonCompatibility
@Version("0.0.1")
class HideCastButtonPatch : BytecodePatch() {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
SwitchPreference(
"revanced_cast_button_enabled",
StringResource("revanced_cast_button_enabled_title", "Show cast button"),
false,
StringResource("revanced_cast_button_summary_on", "Cast button is shown"),
StringResource("revanced_cast_button_summary_off", "Cast button is hidden")
)
)
data.classes.forEach { classDef ->
classDef.methods.forEach { method ->
if (classDef.type.endsWith("MediaRouteButton;") && method.name == "setVisibility") {

View File

@ -15,6 +15,9 @@ import app.revanced.patches.youtube.layout.createbutton.annotations.CreateButton
import app.revanced.patches.youtube.layout.createbutton.fingerprints.CreateButtonFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.mapping.patch.ResourceIdMappingProviderResourcePatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
@ -22,7 +25,7 @@ import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
import org.jf.dexlib2.iface.reference.MethodReference
@Patch
@DependsOn([IntegrationsPatch::class, ResourceIdMappingProviderResourcePatch::class])
@DependsOn([IntegrationsPatch::class, ResourceIdMappingProviderResourcePatch::class, SettingsPatch::class])
@Name("disable-create-button")
@Description("Hides the create button in the navigation bar.")
@CreateButtonCompatibility
@ -33,6 +36,16 @@ class CreateButtonRemoverPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
SwitchPreference(
"revanced_create_button_enabled",
StringResource("revanced_create_button_enabled_title", "Show create button"),
false,
StringResource("revanced_create_button_summary_on", "Create button is shown."),
StringResource("revanced_create_button_summary_off", "Create button is hidden.")
)
)
val result = CreateButtonFingerprint.result!!
// Get the required register which holds the view object we need to pass to the method hideCreateButton

View File

@ -17,10 +17,13 @@ import app.revanced.patches.youtube.layout.fullscreenpanels.annotations.Fullscre
import app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints.FullscreenViewAdderFingerprint
import app.revanced.patches.youtube.layout.fullscreenpanels.fingerprints.FullscreenViewAdderParentFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
@Patch
@Name("disable-fullscreen-panels")
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Description("Disables video description and comments panel in fullscreen view.")
@FullscreenPanelsCompatibility
@Version("0.0.1")
@ -30,6 +33,16 @@ class FullscreenPanelsRemoverPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
SwitchPreference(
"revanced_fullscreen_panels_enabled",
StringResource("revanced_fullscreen_panels_enabled_title", "Show fullscreen panels"),
false,
StringResource("revanced_fullscreen_panels_summary_on", "Fullscreen panels are shown"),
StringResource("revanced_fullscreen_panels_summary_off", "Fullscreen panels are hidden")
)
)
val parentResult = FullscreenViewAdderParentFingerprint.result!!
FullscreenViewAdderFingerprint.resolve(data, parentResult.method, parentResult.classDef)
val result = FullscreenViewAdderParentFingerprint.result

View File

@ -13,10 +13,13 @@ import app.revanced.patcher.patch.impl.BytecodePatch
import app.revanced.patches.youtube.layout.oldqualitylayout.annotations.OldQualityLayoutCompatibility
import app.revanced.patches.youtube.layout.oldqualitylayout.fingerprints.QualityMenuViewInflateFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import org.jf.dexlib2.iface.instruction.FiveRegisterInstruction
@Patch
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Name("old-quality-layout")
@Description("Enables the original quality flyout menu.")
@OldQualityLayoutCompatibility
@ -25,6 +28,16 @@ class OldQualityLayoutPatch : BytecodePatch(
listOf(QualityMenuViewInflateFingerprint)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
SwitchPreference(
"revanced_use_old_style_quality_settings",
StringResource("revanced_old_style_quality_settings_enabled_title", "Use old quality layout"),
true,
StringResource("revanced_old_style_quality_settings_summary_on", "Old quality settings are shown"),
StringResource("revanced_old_style_quality_settings_summary_off", "New quality settings are shown")
)
)
val inflateFingerprintResult = QualityMenuViewInflateFingerprint.result!!
val method = inflateFingerprintResult.mutableMethod
val instructions = method.implementation!!.instructions

View File

@ -7,13 +7,18 @@ import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.impl.BytecodePatch
import app.revanced.patches.youtube.layout.reels.annotations.HideReelsCompatibility
import app.revanced.patches.youtube.layout.reels.fingerprints.HideReelsFingerprint
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
//@Patch TODO: this is currently in the general-bytecode-ads patch due to the integrations having a preference for including reels or not. Move it here.
@Name("hide-reels")
@Description("Hides reels on the home page.")
@DependsOn([SettingsPatch::class])
@HideReelsCompatibility
@Version("0.0.1")
class HideReelsPatch : BytecodePatch(
@ -22,6 +27,16 @@ class HideReelsPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
SwitchPreference(
"revanced_reel_button_enabled",
StringResource("revanced_reel_button_enabled_title", "Show reels button"),
false,
StringResource("revanced_reel_button_summary_on", "Reels button is shown"),
StringResource("revanced_reel_button_summary_off", "Reels button is hidden")
)
)
val result = HideReelsFingerprint.result!!
// HideReel will hide the reel view before it is being used,

View File

@ -12,15 +12,16 @@ import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.patch.impl.BytecodePatch
import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.TextComponentSpecParentFingerprint
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.DislikeFingerprint
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.LikeFingerprint
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.RemoveLikeFingerprint
import app.revanced.patches.youtube.layout.returnyoutubedislike.fingerprints.TextComponentSpecParentFingerprint
import app.revanced.patches.youtube.layout.returnyoutubedislike.resource.patch.ReturnYouTubeDislikeResourcePatch
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.videoid.patch.VideoIdPatch
@Patch
@DependsOn([IntegrationsPatch::class, VideoIdPatch::class])
@DependsOn([IntegrationsPatch::class, VideoIdPatch::class, ReturnYouTubeDislikeResourcePatch::class])
@Name("return-youtube-dislike")
@Description("Shows the dislike count of videos using the Return YouTube Dislike API.")
@ReturnYouTubeDislikeCompatibility
@ -57,9 +58,9 @@ class ReturnYouTubeDislikePatch : BytecodePatch(
val parentResult = TextComponentSpecParentFingerprint.result!!
val createComponentMethod = parentResult.mutableClass.methods.find { method ->
method.parameters.size >= 19 && method.parameterTypes.takeLast(4)
.all { param -> param == "Ljava/util/concurrent/atomic/AtomicReference;" }
}
method.parameters.size >= 19 && method.parameterTypes.takeLast(4)
.all { param -> param == "Ljava/util/concurrent/atomic/AtomicReference;" }
}
?: return PatchResultError("TextComponentSpec.createComponent not found")
val conversionContextParam = 5

View File

@ -0,0 +1,47 @@
package app.revanced.patches.youtube.layout.returnyoutubedislike.resource.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.ResourceData
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.impl.ResourcePatch
import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility
import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.Preference
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.util.resources.ResourceUtils.iterateXmlNodeChildren
@DependsOn([FixLocaleConfigErrorPatch::class, SettingsPatch::class])
@Name("return-youtube-dislike-resource-patch")
@Description("Adds the preferences for Return YouTube Dislike.")
@ReturnYouTubeDislikeCompatibility
@Version("0.0.1")
class ReturnYouTubeDislikeResourcePatch : ResourcePatch() {
override fun execute(data: ResourceData): PatchResult {
val youtubePackage = "com.google.android.youtube"
SettingsPatch.addPreference(
Preference(
StringResource("revanced_ryd_settings_title", "Return YouTube Dislike"),
Preference.Intent(
youtubePackage,
"ryd_settings",
"com.google.android.libraries.social.licenses.LicenseActivity"
),
StringResource("revanced_ryd_settings_summary", "Settings for Return YouTube Dislike"),
)
)
// merge strings
data.iterateXmlNodeChildren("returnyoutubedislike/host/values/strings.xml", "resources") {
// TODO: figure out why this is needed
if (!it.hasAttributes()) return@iterateXmlNodeChildren
val attributes = it.attributes
SettingsPatch.addString(attributes.getNamedItem("name")!!.nodeValue!!, it.textContent!!)
}
return PatchResultSuccess()
}
}

View File

@ -14,11 +14,14 @@ import app.revanced.patches.youtube.layout.shorts.button.annotations.ShortsButto
import app.revanced.patches.youtube.layout.shorts.button.fingerprints.PivotBarButtonTabEnumFingerprint
import app.revanced.patches.youtube.layout.shorts.button.fingerprints.PivotBarButtonsViewFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
import org.jf.dexlib2.Opcode
@Patch
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Name("hide-shorts-button")
@Description("Hides the shorts button on the navigation bar.")
@ShortsButtonCompatibility
@ -29,6 +32,16 @@ class ShortsButtonRemoverPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
SwitchPreference(
"revanced_shorts_button_enabled",
StringResource("revanced_shorts_button_enabled_title", "Show shorts button"),
false,
StringResource("revanced_shorts_button_summary_on", "Shorts button is shown"),
StringResource("revanced_shorts_button_summary_off", "Shorts button is hidden")
)
)
val tabEnumResult = PivotBarButtonTabEnumFingerprint.result!!
val tabEnumImplementation = tabEnumResult.mutableMethod.implementation!!
val scanResultEndIndex = tabEnumResult.patternScanResult!!.endIndex

View File

@ -9,96 +9,112 @@ import app.revanced.patcher.patch.annotations.DependsOn
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 app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.Preference
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.util.resources.ResourceUtils
import app.revanced.util.resources.ResourceUtils.copyResources
import app.revanced.util.resources.ResourceUtils.copyXmlNode
import java.nio.file.Files
import app.revanced.util.resources.ResourceUtils.iterateXmlNodeChildren
@Name("sponsorblock-resource-patch")
@SponsorBlockCompatibility
@DependsOn([FixLocaleConfigErrorPatch::class])
@DependsOn([FixLocaleConfigErrorPatch::class, SettingsPatch::class])
@Version("0.0.1")
class SponsorBlockResourcePatch : ResourcePatch() {
override fun execute(data: ResourceData): PatchResult {
val youtubePackage = "com.google.android.youtube"
SettingsPatch.addPreference(
Preference(
StringResource("sb_settings", "SponsorBlock"),
Preference.Intent(
youtubePackage,
"sponsorblock_settings",
"com.google.android.libraries.social.licenses.LicenseActivity"
),
StringResource("revanced_sponsorblock_settings_summary", "SponsorBlock related settings"),
)
)
val classLoader = this.javaClass.classLoader
/*
merge SponsorBlock strings to main strings
*/
val stringsResourcePath = "values/strings.xml"
val stringsResourceInputStream = classLoader.getResourceAsStream("sponsorblock/$stringsResourcePath")!!
data.iterateXmlNodeChildren("sponsorblock/host/values/strings.xml", "resources") {
// TODO: figure out why this is needed
if (!it.hasAttributes()) return@iterateXmlNodeChildren
// copy nodes from the resources node to the real resource node
"resources".copyXmlNode(
data.xmlEditor[stringsResourceInputStream],
data.xmlEditor["res/$stringsResourcePath"]
).close() // close afterwards
val attributes = it.attributes
val key = attributes.getNamedItem("name")!!.nodeValue!!
val value = it.textContent!!
// all strings of SponsorBlock which have this attribute have the attribute value false,
// hence a null check suffices
val formatted = attributes.getNamedItem("formatted") == null
SettingsPatch.addString(key, value, formatted)
}
/*
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()
)
}
arrayOf(
ResourceUtils.ResourceGroup(
"layout",
"inline_sponsor_overlay.xml",
"new_segment.xml",
"skip_sponsor_button.xml"
),
ResourceUtils.ResourceGroup(
// required resource for back button, because when the base APK is used, this resource will not exist
"drawable",
"ic_sb_adjust.xml",
"ic_sb_compare.xml",
"ic_sb_edit.xml",
"ic_sb_logo.xml",
"ic_sb_publish.xml",
"ic_sb_voting.xml"
),
ResourceUtils.ResourceGroup(
// required resource for back button, because when the base APK is used, this resource will not exist
"drawable-xxxhdpi", "quantum_ic_skip_next_white_24.png"
)
).forEach { resourceGroup ->
data.copyResources("sponsorblock", resourceGroup)
}
/*
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 hostingResourceStream =
classLoader.getResourceAsStream("sponsorblock/host/layout/youtube_controls_layout.xml")!!
val targetXmlEditor = data.xmlEditor["res/$path/$resource.xml"]
"RelativeLayout".copyXmlNode(
data.xmlEditor[hostingResourceStream],
targetXmlEditor
).also {
val children = targetXmlEditor.file.getElementsByTagName("RelativeLayout").item(0).childNodes
val targetXmlEditor = data.xmlEditor["res/layout/youtube_controls_layout.xml"]
"RelativeLayout".copyXmlNode(
data.xmlEditor[hostingResourceStream],
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 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.hasAttributes() && view.attributes.getNamedItem("android:id").nodeValue.endsWith("live_chat_overlay_button"))) continue
// Replace the attribute for a specific node only
if (!(view.hasAttributes() && 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"
// 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
view.attributes.getNamedItem("android:layout_toStartOf").nodeValue = votingButtonId
break
}
}.close() // close afterwards
break
}
}
}.close() // close afterwards
return PatchResultSuccess()
}
}

View File

@ -17,9 +17,12 @@ import app.revanced.patches.youtube.layout.watermark.annotations.HideWatermarkCo
import app.revanced.patches.youtube.layout.watermark.fingerprints.HideWatermarkParentFingerprint
import app.revanced.patches.youtube.layout.watermark.fingerprints.HideWatermarkFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
@Patch
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Name("hide-watermark")
@Description("Hides creator's watermarks on videos.")
@HideWatermarkCompatibility
@ -30,6 +33,16 @@ class HideWatermarkPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
SwitchPreference(
"revanced_branding_watermark_enabled",
StringResource("revanced_branding_watermark_enabled_title", "Show branding watermark"),
false,
StringResource("revanced_branding_watermark_summary_on", "Branding watermark is shown"),
StringResource("revanced_branding_watermark_summary_off", "Branding watermark is hidden")
)
)
HideWatermarkFingerprint.resolve(data, HideWatermarkParentFingerprint.result!!.classDef)
val result = HideWatermarkFingerprint.result
?: return PatchResultError("Required parent method could not be found.")

View File

@ -3,10 +3,8 @@ package app.revanced.patches.youtube.layout.widesearchbar.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patches.youtube.layout.reels.annotations.HideReelsCompatibility
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.widesearchbar.annotations.WideSearchbarCompatibility
import org.jf.dexlib2.AccessFlags
@ -14,7 +12,6 @@ import org.jf.dexlib2.AccessFlags
@MatchingMethod(
"Lkrf;", "i"
)
@FuzzyPatternScanMethod(3)
@WideSearchbarCompatibility
@Version("0.0.1")

View File

@ -19,9 +19,12 @@ import app.revanced.patches.youtube.layout.widesearchbar.fingerprints.WideSearch
import app.revanced.patches.youtube.layout.widesearchbar.fingerprints.WideSearchbarTwoFingerprint
import app.revanced.patches.youtube.layout.widesearchbar.fingerprints.WideSearchbarTwoParentFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
@Patch
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@Name("enable-wide-searchbar")
@Description("Replaces the search icon with a wide search bar. This will hide the YouTube logo when active.")
@WideSearchbarCompatibility
@ -32,6 +35,16 @@ class WideSearchbarPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
SwitchPreference(
"revanced_wide_searchbar_enabled",
StringResource("revanced_wide_searchbar_enabled_title", "Enable wide search bar"),
false,
StringResource("revanced_wide_searchbar_summary_on", "Wide search bar is enabled"),
StringResource("revanced_wide_searchbar_summary_off", "Wide search bar is disabled")
)
)
WideSearchbarOneFingerprint.resolve(data, WideSearchbarOneParentFingerprint.result!!.classDef)
WideSearchbarTwoFingerprint.resolve(data, WideSearchbarTwoParentFingerprint.result!!.classDef)

View File

@ -17,6 +17,9 @@ import app.revanced.patches.youtube.misc.autorepeat.annotations.AutoRepeatCompat
import app.revanced.patches.youtube.misc.autorepeat.fingerprints.AutoRepeatFingerprint
import app.revanced.patches.youtube.misc.autorepeat.fingerprints.AutoRepeatParentFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
@Patch
@DependsOn([IntegrationsPatch::class])
@ -30,6 +33,16 @@ class AutoRepeatPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.MISC.addPreferences(
SwitchPreference(
"revanced_pref_auto_repeat",
StringResource("revanced_auto_repeat_enabled_title", "Enable auto-repeat"),
true,
StringResource("revanced_auto_repeat_summary_on", "Auto-repeat is enabled"),
StringResource("revanced_auto_repeat_summary_off", "Auto-repeat is disabled")
)
)
//Get Result from the ParentFingerprint which is the playMethod we need to get.
val parentResult = AutoRepeatParentFingerprint.result
?: return PatchResultError("ParentFingerprint did not resolve.")

View File

@ -35,6 +35,8 @@ class CustomPlaybackSpeedPatch : BytecodePatch(
) {
override fun execute(data: BytecodeData): PatchResult {
//TODO: include setting to skip remembering the new speed
val arrayGenMethod = SpeedArrayGeneratorFingerprint.result?.mutableMethod!!
val arrayGenMethodImpl = arrayGenMethod.implementation!!

View File

@ -14,6 +14,9 @@ import app.revanced.patcher.patch.impl.BytecodePatch
import app.revanced.patches.youtube.misc.hdrbrightness.annotations.HDRBrightnessCompatibility
import app.revanced.patches.youtube.misc.hdrbrightness.fingerprints.HDRBrightnessFingerprintXXZ
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.iface.instruction.TwoRegisterInstruction
import org.jf.dexlib2.iface.reference.FieldReference
@ -23,13 +26,23 @@ import org.jf.dexlib2.iface.reference.FieldReference
@Description("Makes the brightness of HDR videos follow the system default.")
@HDRBrightnessCompatibility
@Version("0.0.2")
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
class HDRBrightnessPatch : BytecodePatch(
listOf(
HDRBrightnessFingerprintXXZ
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.MISC.addPreferences(
SwitchPreference(
"revanced_pref_hdr_autobrightness",
StringResource("revanced_hdr_autobrightness_enabled_title", "Enable auto HDR brightness"),
true,
StringResource("revanced_hdr_autobrightness_summary_on", "Auto HDR brightness is enabled"),
StringResource("revanced_hdr_autobrightness_summary_off", "Auto HDR brightness is disabled")
)
)
val method = HDRBrightnessFingerprintXXZ.result?.mutableMethod
?: return PatchResultError("HDRBrightnessFingerprint could not resolve the method!")

View File

@ -28,8 +28,7 @@ class FixLocaleConfigErrorPatch : ResourcePatch() {
// by replacing the attributes name
val attribute = "android:localeConfig"
applicationNode.setAttribute("localeConfig", applicationNode.getAttribute(attribute))
applicationNode.removeAttribute("android:localeConfig")
applicationNode.removeAttribute(attribute)
}
return PatchResultSuccess()

View File

@ -13,6 +13,9 @@ import app.revanced.patches.youtube.misc.microg.annotations.MicroGPatchCompatibi
import app.revanced.patches.youtube.misc.microg.shared.Constants.BASE_MICROG_PACKAGE_NAME
import app.revanced.patches.youtube.misc.microg.shared.Constants.REVANCED_PACKAGE_NAME
import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.Preference
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
@Name("microg-resource-patch")
@DependsOn([FixLocaleConfigErrorPatch::class, SettingsResourcePatch::class])
@ -21,25 +24,14 @@ import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourc
@Version("0.0.1")
class MicroGResourcePatch : ResourcePatch() {
override fun execute(data: ResourceData): PatchResult {
data.xmlEditor["res/xml/settings_fragment.xml"].use {
val settingsElementIntent = it.file.createElement("intent")
settingsElementIntent.setAttribute("android:targetPackage", "$BASE_MICROG_PACKAGE_NAME.android.gms")
settingsElementIntent.setAttribute("android:targetClass", "org.microg.gms.ui.SettingsActivity")
val settingsElement = it.file.createElement("Preference")
settingsElement.setAttribute("android:title", "@string/microg_settings")
settingsElement.appendChild(settingsElementIntent)
it.file.firstChild.appendChild(settingsElement)
}
val settingsFragment = data["res/xml/settings_fragment.xml"]
settingsFragment.writeText(
settingsFragment.readText().replace(
"android:targetPackage=\"com.google.android.youtube",
"android:targetPackage=\"$REVANCED_PACKAGE_NAME"
SettingsPatch.addPreference(
Preference(
StringResource("microg_settings", "MicroG Settings"),
Preference.Intent("$BASE_MICROG_PACKAGE_NAME.android.gms", "", "org.microg.gms.ui.SettingsActivity"),
StringResource("microg_settings_summary", "Settings for MicroG"),
)
)
SettingsPatch.renameIntentsTargetPackage(REVANCED_PACKAGE_NAME)
val manifest = data["AndroidManifest.xml"]
manifest.writeText(

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.minimizedplayback.annotations
package app.revanced.patches.youtube.misc.minimizedplayback.annotations
import app.revanced.patcher.annotation.Compatibility
import app.revanced.patcher.annotation.Package

View File

@ -1,11 +1,11 @@
package app.revanced.patches.youtube.layout.minimizedplayback.fingerprints
package app.revanced.patches.youtube.misc.minimizedplayback.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.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.minimizedplayback.annotations.MinimizedPlaybackCompatibility
import app.revanced.patches.youtube.misc.minimizedplayback.annotations.MinimizedPlaybackCompatibility
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.minimizedplayback.fingerprints
package app.revanced.patches.youtube.misc.minimizedplayback.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
@ -6,7 +6,7 @@ import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patches.youtube.layout.minimizedplayback.annotations.MinimizedPlaybackCompatibility
import app.revanced.patches.youtube.misc.minimizedplayback.annotations.MinimizedPlaybackCompatibility
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.minimizedplayback.fingerprints
package app.revanced.patches.youtube.misc.minimizedplayback.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
@ -6,10 +6,9 @@ import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.minimizedplayback.annotations.MinimizedPlaybackCompatibility
import app.revanced.patches.youtube.misc.minimizedplayback.annotations.MinimizedPlaybackCompatibility
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
@Name("minimized-playback-manager-fingerprint")
@MatchingMethod(

View File

@ -1,4 +1,4 @@
package app.revanced.patches.youtube.layout.minimizedplayback.patch
package app.revanced.patches.youtube.misc.minimizedplayback.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
@ -12,11 +12,14 @@ 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.patches.youtube.layout.minimizedplayback.annotations.MinimizedPlaybackCompatibility
import app.revanced.patches.youtube.layout.minimizedplayback.fingerprints.MinimizedPlaybackKidsFingerprint
import app.revanced.patches.youtube.layout.minimizedplayback.fingerprints.MinimizedPlaybackManagerFingerprint
import app.revanced.patches.youtube.layout.minimizedplayback.fingerprints.MinimizedPlaybackSettingsFingerprint
import app.revanced.patches.youtube.misc.minimizedplayback.annotations.MinimizedPlaybackCompatibility
import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackKidsFingerprint
import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackManagerFingerprint
import app.revanced.patches.youtube.misc.minimizedplayback.fingerprints.MinimizedPlaybackSettingsFingerprint
import app.revanced.patches.youtube.misc.integrations.patch.IntegrationsPatch
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.SwitchPreference
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.iface.reference.MethodReference
@ -24,7 +27,7 @@ import org.jf.dexlib2.iface.reference.MethodReference
@Patch
@Name("minimized-playback")
@Description("Enables minimized and background playback.")
@DependsOn([IntegrationsPatch::class])
@DependsOn([IntegrationsPatch::class, SettingsPatch::class])
@MinimizedPlaybackCompatibility
@Version("0.0.1")
class MinimizedPlaybackPatch : BytecodePatch(
@ -33,6 +36,16 @@ class MinimizedPlaybackPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.MISC.addPreferences(
SwitchPreference(
"revanced_enable_minimized_playback",
StringResource("revanced_minimized_playback_enabled_title", "Enable minimized playback"),
true,
StringResource("revanced_minimized_playback_summary_on", "Minimized playback is enabled"),
StringResource("revanced_minimized_playback_summary_off", "Minimized playback is disabled")
)
)
// Instead of removing all instructions like Vanced,
// we return the method at the beginning instead
MinimizedPlaybackManagerFingerprint.result!!.mutableMethod.addInstructions(

View File

@ -33,6 +33,8 @@ class RememberVideoQualityPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
//TODO: include setting to skip remembering the new quality
val setterMethod = VideoQualitySetterFingerprint.result!!
VideoUserQualityChangeFingerprint.resolve(data, setterMethod.classDef)

View File

@ -4,7 +4,7 @@ import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility
import app.revanced.patches.youtube.misc.settings.annotations.SettingsCompatibility
// TODO: This is more of a class fingerprint than a method fingerprint.
// Convert to a class fingerprint whenever possible.
@ -12,7 +12,7 @@ import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.Retu
@MatchingMethod(
"Lcom/google/android/libraries/social/licenses/LicenseActivity;", "onCreate"
)
@ReturnYouTubeDislikeCompatibility
@SettingsCompatibility
@Version("0.0.1")
object LicenseActivityFingerprint : MethodFingerprint(
null,

View File

@ -2,10 +2,9 @@ package app.revanced.patches.youtube.misc.settings.bytecode.fingerprints
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility
import app.revanced.patches.youtube.misc.settings.annotations.SettingsCompatibility
// TODO: This is more of a class fingerprint than a method fingerprint.
// Convert to a class fingerprint whenever possible.
@ -13,7 +12,7 @@ import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.Retu
@MatchingMethod(
"Lapp/revanced/integrations/settingsmenu/ReVancedSettingActivity;", "initializeSettings"
)
@ReturnYouTubeDislikeCompatibility
@SettingsCompatibility
@Version("0.0.1")
object ReVancedSettingsActivityFingerprint : MethodFingerprint(
null,

View File

@ -4,7 +4,7 @@ import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.youtube.layout.returnyoutubedislike.annotations.ReturnYouTubeDislikeCompatibility
import app.revanced.patches.youtube.misc.settings.annotations.SettingsCompatibility
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import org.jf.dexlib2.Opcode
import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
@ -13,7 +13,7 @@ import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
@MatchingMethod(
"Lfyq;", "a"
)
@ReturnYouTubeDislikeCompatibility
@SettingsCompatibility
@Version("0.0.1")
object ThemeSetterFingerprint : MethodFingerprint(
"L",

View File

@ -18,14 +18,20 @@ import app.revanced.patches.youtube.misc.settings.annotations.SettingsCompatibil
import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.LicenseActivityFingerprint
import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ReVancedSettingsActivityFingerprint
import app.revanced.patches.youtube.misc.settings.bytecode.fingerprints.ThemeSetterFingerprint
import app.revanced.patches.youtube.misc.settings.framework.components.BasePreference
import app.revanced.patches.youtube.misc.settings.framework.components.impl.ArrayResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.Preference
import app.revanced.patches.youtube.misc.settings.framework.components.impl.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourcePatch
import org.jf.dexlib2.util.MethodUtil
import java.io.Closeable
@Patch
@DependsOn(
[
IntegrationsPatch::class,
SettingsResourcePatch::class,
ResourceIdMappingProviderResourcePatch::class
]
)
@Name("settings")
@ -34,7 +40,7 @@ import app.revanced.patches.youtube.misc.settings.resource.patch.SettingsResourc
@Version("0.0.1")
class SettingsPatch : BytecodePatch(
listOf(LicenseActivityFingerprint, ReVancedSettingsActivityFingerprint, ThemeSetterFingerprint)
) {
), Closeable {
override fun execute(data: BytecodeData): PatchResult {
val licenseActivityResult = LicenseActivityFingerprint.result!!
val settingsResult = ReVancedSettingsActivityFingerprint.result!!
@ -65,7 +71,7 @@ class SettingsPatch : BytecodePatch(
)
}
// add the setTheme call to the onCreate method to not affect the offsets.
// add the setTheme call to the onCreate method to not affect the offsets
onCreate.addInstructions(
1,
"""
@ -74,18 +80,76 @@ class SettingsPatch : BytecodePatch(
"""
)
// add the initializeSettings call to the onCreate method.
// add the initializeSettings call to the onCreate method
onCreate.addInstruction(
0,
"invoke-static { p0 }, ${settingsClass.type}->$setThemeMethodName(${licenseActivityClass.type})V"
)
// get rid of, now, useless overridden methods
licenseActivityResult.mutableClass.methods.removeIf { it.name != "onCreate" && !MethodUtil.isConstructor(it) }
return PatchResultSuccess()
}
internal companion object {
val appearanceStringId = ResourceIdMappingProviderResourcePatch.resourceMappings.find {
// TODO: hide this somehow
var appearanceStringId: Long = ResourceIdMappingProviderResourcePatch.resourceMappings.find {
it.type == "string" && it.name == "app_theme_appearance_dark"
}!!.id
fun addString(identifier: String, value: String, formatted: Boolean = true) =
SettingsResourcePatch.addString(identifier, value, formatted)
fun addPreferenceScreen(preferenceScreen: app.revanced.patches.youtube.misc.settings.framework.components.impl.PreferenceScreen) =
SettingsResourcePatch.addPreferenceScreen(preferenceScreen)
fun addPreference(preference: Preference) =
SettingsResourcePatch.addPreference(preference)
fun addArray(arrayResource: ArrayResource) =
SettingsResourcePatch.addArray(arrayResource)
fun renameIntentsTargetPackage(newPackage: String) {
SettingsResourcePatch.overrideIntentsTargetPackage = newPackage
}
}
/**
* Preference screens patches should add their settings to.
*/
internal enum class PreferenceScreen(
private val key: String,
private val title: String,
private val summary: String? = null,
private val preferences: MutableList<BasePreference> = mutableListOf()
) : Closeable {
ADS("ads", "Ads", "Ad related settings"),
INTERACTIONS("interactions", "Interaction", "Settings related to interactions"),
LAYOUT("layout", "Layout", "Settings related to the layout"),
MISC("misc", "Miscellaneous", "Miscellaneous patches");
override fun close() {
if (preferences.size == 0) return
addPreferenceScreen(
PreferenceScreen(
key,
StringResource("${key}_title", title),
preferences,
summary?.let { summary ->
StringResource("${key}_summary", summary)
}
)
)
}
/**
* Add preferences to the preference screen.
*/
fun addPreferences(vararg preferences: BasePreference) = this.preferences.addAll(preferences)
}
override fun close() = PreferenceScreen.values().forEach(PreferenceScreen::close)
}

View File

@ -0,0 +1,14 @@
package app.revanced.patches.youtube.misc.settings.framework.components
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
/**
* Base preference class for all preferences.
*
* @param key The key of the preference.
* @param title The title of the preference.
*/
internal abstract class BasePreference(
override val key: String,
override val title: StringResource,
) : IPreference

View File

@ -0,0 +1,23 @@
package app.revanced.patches.youtube.misc.settings.framework.components
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
/**
* Preference
*/
internal interface IPreference {
/**
* Key of the preference.
*/
val key: String
/**
* Title of the preference.
*/
val title: StringResource
/**
* Tag name of the preference.
*/
val tag: String
}

View File

@ -0,0 +1,9 @@
package app.revanced.patches.youtube.misc.settings.framework.components.impl
/**
* Represents an array resource.
*
* @param name The name of the array resource.
* @param items The items of the array resource.
*/
internal data class ArrayResource(val name: String, val items: List<StringResource>)

View File

@ -0,0 +1,6 @@
package app.revanced.patches.youtube.misc.settings.framework.components.impl
enum class InputType(val type: String) {
STRING("text"),
NUMBER("number"),
}

View File

@ -0,0 +1,18 @@
package app.revanced.patches.youtube.misc.settings.framework.components.impl
/**
* A Preference object.
*
* @param title The title of the preference.
* @param intent The intent of the preference.
* @param summary The summary of the text preference.
*/
internal class Preference(
val title: StringResource,
val intent: Intent,
val summary: StringResource? = null
) {
val tag: String = "Preference"
data class Intent(val targetPackage: String, val data: String, val targetClass: String)
}

View File

@ -0,0 +1,20 @@
package app.revanced.patches.youtube.misc.settings.framework.components.impl
import app.revanced.patches.youtube.misc.settings.framework.components.BasePreference
/**
* Preference screen.
*
* @param key The key of the preference.
* @param title The title of the preference.
* @param preferences Child preferences of this screen.
* @param summary The summary of the text preference.
*/
internal open class PreferenceScreen(
key: String,
title: StringResource,
val preferences: List<BasePreference>,
var summary: StringResource? = null
) : BasePreference(key, title) {
override val tag: String = "PreferenceScreen"
}

View File

@ -0,0 +1,10 @@
package app.revanced.patches.youtube.misc.settings.framework.components.impl
/**
* Represents a string value in the strings.xml file
*
* @param name The name of the string
* @param value The value of the string
* @param formatted If the string is formatted. If false, the attribute will be set
*/
internal data class StringResource(val name: String, val value: String, val formatted: Boolean = true)

View File

@ -0,0 +1,21 @@
package app.revanced.patches.youtube.misc.settings.framework.components.impl
import app.revanced.patches.youtube.misc.settings.framework.components.BasePreference
/**
* Switch preference.
*
* @param key The key of the switch.
* @param title The title of the switch.
* @param default The default value of the switch.
* @param summaryOn The summary to show when the preference is enabled.
* @param summaryOff The summary to show when the preference is disabled.
*/
internal class SwitchPreference(
key: String, title: StringResource,
val default: Boolean = false,
var summaryOn: StringResource? = null,
var summaryOff: StringResource? = null
) : BasePreference(key, title) {
override val tag: String = "SwitchPreference"
}

View File

@ -0,0 +1,22 @@
package app.revanced.patches.youtube.misc.settings.framework.components.impl
import app.revanced.patches.youtube.misc.settings.framework.components.BasePreference
/**
* Text preference.
*
* @param key The key of the text preference.
* @param title The title of the text preference.
* @param inputType The input type of the text preference.
* @param default The default value of the text preference.
* @param summary The summary of the text preference.
*/
internal class TextPreference(
key: String,
title: StringResource,
var inputType: InputType = InputType.STRING,
var default: String? = null,
var summary: StringResource? = null
) : BasePreference(key, title) {
override val tag: String = "EditTextPreference"
}

View File

@ -2,71 +2,290 @@ package app.revanced.patches.youtube.misc.settings.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.DependsOn
import app.revanced.patcher.patch.impl.ResourcePatch
import app.revanced.patches.youtube.misc.manifest.patch.FixLocaleConfigErrorPatch
import app.revanced.patches.youtube.misc.mapping.patch.ResourceIdMappingProviderResourcePatch
import app.revanced.patches.youtube.misc.settings.annotations.SettingsCompatibility
import app.revanced.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.BasePreference
import app.revanced.patches.youtube.misc.settings.framework.components.impl.*
import app.revanced.util.resources.ResourceUtils
import app.revanced.util.resources.ResourceUtils.copyResources
import app.revanced.util.resources.ResourceUtils.copyXmlNode
import org.w3c.dom.Element
import org.w3c.dom.Node
import java.io.Closeable
@Name("settings-resource-patch")
@SettingsCompatibility
@DependsOn([FixLocaleConfigErrorPatch::class])
@DependsOn([FixLocaleConfigErrorPatch::class, ResourceIdMappingProviderResourcePatch::class])
@Version("0.0.1")
class SettingsResourcePatch : ResourcePatch() {
class SettingsResourcePatch : ResourcePatch(), Closeable {
override fun execute(data: ResourceData): PatchResult {
/*
* Copy strings
* create missing directory for the resources
*/
data.copyXmlNode("settings/host", "values/strings.xml", "resources")
data["res/drawable-ldrtl-xxxhdpi"].mkdirs()
/*
* Copy arrays
*/
data.copyXmlNode("settings/host", "values/arrays.xml", "resources")
/*
* Copy preference fragments
*/
data.copyXmlNode("settings/host", "xml/settings_fragment.xml", "PreferenceScreen")
/*
* Copy layout resources
* copy layout resources
*/
arrayOf(
ResourceUtils.ResourceGroup(
"layout",
"xsettings_toolbar.xml",
"xsettings_with_toolbar.xml",
"xsettings_with_toolbar_layout.xml"
),
ResourceUtils.ResourceGroup(
"xml",
"revanced_prefs.xml"
"revanced_settings_toolbar.xml",
"revanced_settings_with_toolbar.xml",
"revanced_settings_with_toolbar_layout.xml"
), ResourceUtils.ResourceGroup(
"xml", "revanced_prefs.xml" // template for new preferences
), ResourceUtils.ResourceGroup(
// required resource for back button, because when the base APK is used, this resource will not exist
"drawable-xxxhdpi", "quantum_ic_arrow_back_white_24.png"
), ResourceUtils.ResourceGroup(
// required resource for back button, because when the base APK is used, this resource will not exist
"drawable-ldrtl-xxxhdpi", "quantum_ic_arrow_back_white_24.png"
)
).forEach { resourceGroup ->
data.copyResources("settings", resourceGroup)
}
data.xmlEditor["AndroidManifest.xml"].use {
val manifestNode = it
.file
.getElementsByTagName("manifest")
.item(0) as Element
val element = it.file.createElement("uses-permission")
element.setAttribute("android:name", "android.permission.SCHEDULE_EXACT_ALARM")
manifestNode.appendChild(element)
data.xmlEditor["AndroidManifest.xml"].use { editor ->
editor.file.getElementsByTagName("manifest").item(0).also {
it.appendChild(it.ownerDocument.createElement("uses-permission").also { element ->
element.setAttribute("android:name", "android.permission.SCHEDULE_EXACT_ALARM")
})
}
}
revancedPreferencesEditor = data.xmlEditor["res/xml/revanced_prefs.xml"]
preferencesEditor = data.xmlEditor["res/xml/settings_fragment.xml"]
stringsEditor = data.xmlEditor["res/values/strings.xml"]
arraysEditor = data.xmlEditor["res/values/arrays.xml"]
// Add the ReVanced settings to the YouTube settings
val youtubePackage = "com.google.android.youtube"
SettingsPatch.addPreference(
Preference(
StringResource("revanced_settings", "ReVanced"),
Preference.Intent(
youtubePackage, "revanced_settings", "com.google.android.libraries.social.licenses.LicenseActivity"
),
StringResource("revanced_settings_summary", "ReVanced specific settings"),
)
)
return PatchResultSuccess()
}
internal companion object {
// if this is not null, all intents will be renamed to this
var overrideIntentsTargetPackage: String? = null
private var revancedPreferenceNode: Node? = null
private var preferencesNode: Node? = null
private var stringsNode: Node? = null
private var arraysNode: Node? = null
private var strings = mutableListOf<StringResource>()
private var revancedPreferencesEditor: DomFileEditor? = null
set(value) {
field = value
revancedPreferenceNode = value.getNode("PreferenceScreen")
}
private var preferencesEditor: DomFileEditor? = null
set(value) {
field = value
preferencesNode = value.getNode("PreferenceScreen")
}
private var stringsEditor: DomFileEditor? = null
set(value) {
field = value
stringsNode = value.getNode("resources")
}
private var arraysEditor: DomFileEditor? = null
set(value) {
field = value
arraysNode = value.getNode("resources")
}
/**
* Add a new string to the resources.
*
* @param identifier The key of the string.
* @param value The value of the string.
* @throws IllegalArgumentException if the string already exists.
*/
fun addString(identifier: String, value: String, formatted: Boolean) =
StringResource(identifier, value, formatted).include()
/**
* Add an array to the resources.
*
* @param arrayResource The array resource to add.
*/
fun addArray(arrayResource: ArrayResource) {
arraysNode!!.appendChild(arraysNode!!.ownerDocument.createElement("string-array").also { arrayNode ->
arrayResource.items.forEach { item ->
item.include()
arrayNode.setAttribute("name", item.name)
arrayNode.appendChild(arrayNode.ownerDocument.createElement("item").also { itemNode ->
itemNode.textContent = item.value
})
}
})
}
/**
* Add a preference screen to the settings.
*
* @param preferenceScreen The name of the preference screen.
*/
fun addPreferenceScreen(preferenceScreen: PreferenceScreen) =
revancedPreferenceNode!!.addPreference(preferenceScreen)
/**
* Add a preference fragment to the preferences.
*
* @param preference The preference to add.
*/
fun addPreference(preference: Preference) {
preferencesNode!!.appendChild(preferencesNode.createElement(preference.tag).also { preferenceNode ->
preferenceNode.setAttribute(
"android:title", "@string/${preference.title.also { it.include() }.name}"
)
preference.summary?.let { summary ->
preferenceNode.setAttribute("android:summary", "@string/${summary.also { it.include() }.name}")
}
preferenceNode.appendChild(preferenceNode.createElement("intent").also { intentNode ->
intentNode.setAttribute("android:targetPackage", preference.intent.targetPackage)
intentNode.setAttribute("android:data", preference.intent.data)
intentNode.setAttribute("android:targetClass", preference.intent.targetClass)
})
})
}
/**
* Add a preference to the settings.
*
* @param preference The preference to add.
*/
private fun Node.addPreference(preference: BasePreference) {
// add a summary to the element
fun Element.addSummary(summaryResource: StringResource?, summaryType: SummaryType = SummaryType.DEFAULT) =
summaryResource?.let { summary ->
setAttribute("android:${summaryType.type}", "@string/${summary.also { it.include() }.name}")
}
fun <T> Element.addDefault(default: T) {
default?.let {
setAttribute(
"android:defaultValue", when (it) {
is Boolean -> if (it) "true" else "false"
is String -> it
else -> throw IllegalArgumentException("Unsupported default value type: ${it::class.java.name}")
}
)
}
}
val preferenceElement = ownerDocument.createElement(preference.tag)
preferenceElement.setAttribute("android:key", preference.key)
preferenceElement.setAttribute("android:title", "@string/${preference.title.also { it.include() }.name}")
when (preference) {
is PreferenceScreen -> {
for (childPreference in preference.preferences) preferenceElement.addPreference(childPreference)
preferenceElement.addSummary(preference.summary)
}
is SwitchPreference -> {
preferenceElement.addDefault(preference.default)
preferenceElement.addSummary(preference.summaryOn, SummaryType.ON)
preferenceElement.addSummary(preference.summaryOff, SummaryType.OFF)
}
is TextPreference -> {
preferenceElement.setAttribute("android:inputType", preference.inputType.type)
preferenceElement.addDefault(preference.default)
preferenceElement.addSummary(preference.summary)
}
}
appendChild(preferenceElement)
}
/**
* Add a new string to the resources.
*
* @throws IllegalArgumentException if the string already exists.
*/
private fun StringResource.include() {
if (strings.any { it.name == name }) return
strings.add(this)
}
private fun DomFileEditor?.getNode(tagName: String) = this!!.file.getElementsByTagName(tagName).item(0)
private fun Node?.createElement(tagName: String) = this!!.ownerDocument.createElement(tagName)
private enum class SummaryType(val type: String) {
DEFAULT("summary"), ON("summaryOn"), OFF("summaryOff")
}
}
override fun close() {
// merge all strings, skip duplicates
strings.forEach { stringResource ->
stringsNode!!.appendChild(stringsNode!!.ownerDocument.createElement("string").also { stringElement ->
stringElement.setAttribute("name", stringResource.name)
// if the string is un-formatted, explicitly add the formatted attribute
if (!stringResource.formatted) stringElement.setAttribute("formatted", "false")
stringElement.textContent = stringResource.value
})
}
// rename the intent package names if it was set
overrideIntentsTargetPackage?.let { packageName ->
val preferences = preferencesEditor!!.getNode("PreferenceScreen").childNodes
for (i in 1 until preferences.length) {
val preferenceNode = preferences.item(i)
// preferences have a child node with the intent tag, skip over every other node
if (preferenceNode.childNodes.length == 0) continue
val intentNode = preferenceNode.firstChild
// if the node doesn't have a target package attribute, skip it
val targetPackageAttribute = intentNode.attributes.getNamedItem("android:targetPackage") ?: continue
// do not replace intent target package if the package name is not from YouTube
val youtubePackage = "com.google.android.youtube"
if (targetPackageAttribute.nodeValue != youtubePackage) continue
// replace the target package name
intentNode.attributes.setNamedItem(preferenceNode.ownerDocument.createAttribute("android:targetPackage")
.also { attribute ->
attribute.value = packageName
})
}
}
revancedPreferencesEditor?.close()
preferencesEditor?.close()
stringsEditor?.close()
arraysEditor?.close()
}
}

View File

@ -7,8 +7,14 @@ import app.revanced.patcher.data.impl.BytecodeData
import app.revanced.patcher.extensions.addInstructions
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.patches.youtube.misc.settings.bytecode.patch.SettingsPatch
import app.revanced.patches.youtube.misc.settings.framework.components.impl.InputType
import app.revanced.patches.youtube.misc.settings.framework.components.impl.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.framework.components.impl.StringResource
import app.revanced.patches.youtube.misc.settings.framework.components.impl.TextPreference
import app.revanced.patches.youtube.misc.videobuffer.annotations.CustomVideoBufferCompatibility
import app.revanced.patches.youtube.misc.videobuffer.fingerprints.MaxBufferFingerprint
import app.revanced.patches.youtube.misc.videobuffer.fingerprints.PlaybackBufferFingerprint
@ -18,6 +24,7 @@ import org.jf.dexlib2.iface.instruction.OneRegisterInstruction
@Patch
@Name("custom-video-buffer")
@Description("Lets you change the buffers of videos.")
@DependsOn([SettingsPatch::class])
@CustomVideoBufferCompatibility
@Version("0.0.1")
class CustomVideoBufferPatch : BytecodePatch(
@ -26,9 +33,52 @@ class CustomVideoBufferPatch : BytecodePatch(
)
) {
override fun execute(data: BytecodeData): PatchResult {
SettingsPatch.PreferenceScreen.MISC.addPreferences(
PreferenceScreen(
"revanced_custom_video_buffer",
StringResource("revanced_custom_video_buffer_title", "Video buffer settings"),
listOf(
TextPreference(
"revanced_pref_max_buffer_ms",
StringResource("revanced_pref_max_buffer_ms_title", "Maximum buffer size"),
InputType.NUMBER,
"120000",
StringResource(
"revanced_pref_max_buffer_ms_summary",
"The maximum size of a buffer for playback"
)
),
TextPreference(
"revanced_pref_buffer_for_playback_ms",
StringResource("revanced_pref_buffer_for_playback_ms_title", "Maximum buffer for playback"),
InputType.NUMBER,
"2500",
StringResource(
"revanced_pref_buffer_for_playback_ms_summary",
"Maximum size of a buffer for playback"
)
),
TextPreference(
"revanced_pref_buffer_for_playback_after_rebuffer_ms",
StringResource(
"revanced_pref_buffer_for_playback_after_rebuffer_ms_title",
"Maximum buffer for playback after rebuffer"
),
InputType.NUMBER,
"5000",
StringResource(
"revanced_pref_buffer_for_playback_after_rebuffer_ms_summary",
"Maximum size of a buffer for playback after rebuffering"
)
)
),
StringResource("revanced_custom_video_buffer_summary", "Custom settings for video buffer")
)
)
execMaxBuffer()
execPlaybackBuffer(data)
execReBuffer(data)
execPlaybackBuffer()
execReBuffer()
return PatchResultSuccess()
}
@ -36,7 +86,7 @@ class CustomVideoBufferPatch : BytecodePatch(
val result = MaxBufferFingerprint.result!!
val method = result.mutableMethod
val index = result.patternScanResult!!.endIndex - 1
val register = (method.implementation!!.instructions.get(index) as OneRegisterInstruction).registerA
val register = (method.implementation!!.instructions[index] as OneRegisterInstruction).registerA
method.addInstructions(
index + 1, """
invoke-static {}, Lapp/revanced/integrations/patches/VideoBufferPatch;->getMaxBuffer()I
@ -45,11 +95,11 @@ class CustomVideoBufferPatch : BytecodePatch(
)
}
private fun execPlaybackBuffer(data: BytecodeData) {
private fun execPlaybackBuffer() {
val result = PlaybackBufferFingerprint.result!!
val method = result.mutableMethod
val index = result.patternScanResult!!.startIndex
val register = (method.implementation!!.instructions.get(index) as OneRegisterInstruction).registerA
val register = (method.implementation!!.instructions[index] as OneRegisterInstruction).registerA
method.addInstructions(
index + 1, """
invoke-static {}, Lapp/revanced/integrations/patches/VideoBufferPatch;->getPlaybackBuffer()I
@ -58,7 +108,7 @@ class CustomVideoBufferPatch : BytecodePatch(
)
}
private fun execReBuffer(data: BytecodeData) {
private fun execReBuffer() {
val result = ReBufferFingerprint.result!!
val method = result.mutableMethod
val index = result.patternScanResult!!.startIndex