From 71ce8230a959dcaf2d8cd5dad1a4f21b88819aa0 Mon Sep 17 00:00:00 2001 From: rospino74 <34315725+rospino74@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:36:37 +0100 Subject: [PATCH] feat(YouTube Music): Add `Hide layout components` patch (#6365) Co-authored-by: oSumAtrIX --- .../extension/music/settings/Settings.java | 4 +- .../patches/components/CustomFilter.java | 16 +- .../shared/patches/litho}/Filter.java | 11 +- .../shared/patches/litho/FilterGroup.java | 213 +++++++ .../patches/litho}/FilterGroupList.java | 48 +- .../patches/litho}/LithoFilterPatch.java | 10 +- .../settings/YouTubeAndMusicSettings.java | 14 + .../youtube/patches/components/AdsFilter.java | 7 +- .../AdvancedVideoQualityMenuFilter.java | 4 +- .../patches/components/ButtonsFilter.java | 8 +- .../patches/components/CommentsFilter.java | 8 +- .../DescriptionComponentsFilter.java | 9 +- .../patches/components/FilterGroup.java | 214 ------- .../components/HideInfoCardsFilter.java | 2 + .../components/KeywordContentFilter.java | 8 +- .../components/LayoutComponentsFilter.java | 5 +- .../components/PlaybackSpeedMenuFilter.java | 4 +- .../PlayerFlyoutMenuItemsFilter.java | 7 +- .../ReturnYouTubeDislikeFilter.java | 7 +- .../patches/components/ShortsFilter.java | 5 +- .../extension/youtube/settings/Settings.java | 20 +- patches/api/patches.api | 14 + .../hide/general/HideLayoutComponentsPatch.kt | 12 + .../misc/debugging/EnableDebuggingPatch.kt | 21 +- .../misc/litho/filter/LithoFilterPatch.kt | 17 + .../patches/music/shared/Fingerprints.kt | 16 + .../hide/general/HideLayoutComponentsPatch.kt | 55 ++ .../misc/debugging/EnableDebuggingPatch.kt | 72 +-- .../shared/misc/litho/filter/Fingerprints.kt | 58 ++ .../misc/litho/filter/LithoFilterPatch.kt | 211 +++++++ .../youtube/ad/general/HideAdsPatch.kt | 2 +- .../layout/buttons/action/HideButtonsPatch.kt | 2 +- .../hide/general/HideLayoutComponentsPatch.kt | 574 +++++++++--------- .../hide/infocards/HideInfoCardsPatch.kt | 2 +- .../HidePlayerFlyoutMenuPatch.kt | 2 +- .../hide/shorts/HideShortsComponentsPatch.kt | 2 +- .../ReturnYouTubeDislikePatch.kt | 2 +- .../misc/debugging/EnableDebuggingPatch.kt | 31 +- .../youtube/misc/litho/filter/Fingerprints.kt | 50 -- .../misc/litho/filter/LithoFilterPatch.kt | 257 ++------ .../quality/AdvancedVideoQualityMenuPatch.kt | 2 +- .../speed/custom/CustomPlaybackSpeedPatch.kt | 2 +- .../resources/addresources/values/strings.xml | 38 +- 43 files changed, 1114 insertions(+), 952 deletions(-) rename extensions/{youtube/src/main/java/app/revanced/extension/youtube => shared/library/src/main/java/app/revanced/extension/shared}/patches/components/CustomFilter.java (91%) rename extensions/{youtube/src/main/java/app/revanced/extension/youtube/patches/components => shared/library/src/main/java/app/revanced/extension/shared/patches/litho}/Filter.java (85%) create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroup.java rename extensions/{youtube/src/main/java/app/revanced/extension/youtube/patches/components => shared/library/src/main/java/app/revanced/extension/shared/patches/litho}/FilterGroupList.java (56%) rename extensions/{youtube/src/main/java/app/revanced/extension/youtube/patches/components => shared/library/src/main/java/app/revanced/extension/shared/patches/litho}/LithoFilterPatch.java (97%) create mode 100644 extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java delete mode 100644 extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/FilterGroup.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/music/misc/litho/filter/LithoFilterPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/layout/hide/general/HideLayoutComponentsPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/LithoFilterPatch.kt diff --git a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java index bd0c80402..b2f61541a 100644 --- a/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java +++ b/extensions/music/src/main/java/app/revanced/extension/music/settings/Settings.java @@ -4,12 +4,12 @@ import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; import static app.revanced.extension.shared.settings.Setting.parent; -import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.shared.settings.YouTubeAndMusicSettings; import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.EnumSetting; import app.revanced.extension.shared.spoof.ClientType; -public class Settings extends BaseSettings { +public class Settings extends YouTubeAndMusicSettings { // Ads public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true); diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/CustomFilter.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/components/CustomFilter.java similarity index 91% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/CustomFilter.java rename to extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/components/CustomFilter.java index 1fed2e967..bd5388f31 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/CustomFilter.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/components/CustomFilter.java @@ -1,4 +1,4 @@ -package app.revanced.extension.youtube.patches.components; +package app.revanced.extension.shared.patches.components; import static app.revanced.extension.shared.StringRef.str; @@ -15,13 +15,15 @@ import java.util.regex.Pattern; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.ByteTrieSearch; -import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; +import app.revanced.extension.shared.settings.YouTubeAndMusicSettings; +import app.revanced.extension.shared.patches.litho.Filter; /** * Allows custom filtering using a path and optionally a proto buffer string. */ @SuppressWarnings("unused") -final class CustomFilter extends Filter { +public final class CustomFilter extends Filter { private static void showInvalidSyntaxToast(@NonNull String expression) { Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression)); @@ -45,7 +47,7 @@ final class CustomFilter extends Filter { @NonNull @SuppressWarnings("ConstantConditions") static Collection parseCustomFilterGroups() { - String rawCustomFilterText = Settings.CUSTOM_FILTER_STRINGS.get(); + String rawCustomFilterText = YouTubeAndMusicSettings.CUSTOM_FILTER_STRINGS.get(); if (rawCustomFilterText.isBlank()) { return Collections.emptyList(); } @@ -100,7 +102,7 @@ final class CustomFilter extends Filter { ByteTrieSearch bufferSearch; CustomFilterGroup(boolean startsWith, @NonNull String path) { - super(Settings.CUSTOM_FILTER, path); + super(YouTubeAndMusicSettings.CUSTOM_FILTER, path); this.startsWith = startsWith; } @@ -145,7 +147,7 @@ final class CustomFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, + public boolean isFiltered(String identifier, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { // All callbacks are custom filter groups. CustomFilterGroup custom = (CustomFilterGroup) matchedGroup; @@ -159,4 +161,4 @@ final class CustomFilter extends Filter { return custom.bufferSearch.matches(buffer); } -} \ No newline at end of file +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/Filter.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java similarity index 85% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/Filter.java rename to extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java index 003949ce2..1bab32354 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/Filter.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/Filter.java @@ -1,9 +1,12 @@ -package app.revanced.extension.youtube.patches.components; +package app.revanced.extension.shared.patches.litho; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; +import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; + /** * Filters litho based components. * @@ -14,11 +17,11 @@ import java.util.List; * either an identifier or a path. * Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)} * search for the buffer content using either a {@link ByteArrayFilterGroup} (if searching for 1 pattern) - * or a {@link ByteArrayFilterGroupList} (if searching for more than 1 pattern). + * or a {@link FilterGroupList.ByteArrayFilterGroupList} (if searching for more than 1 pattern). * * All callbacks must be registered before the constructor completes. */ -abstract class Filter { +public abstract class Filter { public enum FilterContentType { IDENTIFIER, @@ -65,7 +68,7 @@ abstract class Filter { * @param contentIndex Matched index of the identifier or path. * @return True if the litho component should be filtered out. */ - boolean isFiltered(String identifier, String path, byte[] buffer, + public boolean isFiltered(String identifier, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { return true; } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroup.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroup.java new file mode 100644 index 000000000..34219de9d --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroup.java @@ -0,0 +1,213 @@ +package app.revanced.extension.shared.patches.litho; + +import androidx.annotation.NonNull; +import app.revanced.extension.shared.ByteTrieSearch; +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.settings.BooleanSetting; + +public abstract class FilterGroup { + public final static class FilterGroupResult { + private BooleanSetting setting; + private int matchedIndex; + private int matchedLength; + // In the future it might be useful to include which pattern matched, + // but for now that is not needed. + + FilterGroupResult() { + this(null, -1, 0); + } + + FilterGroupResult(BooleanSetting setting, int matchedIndex, int matchedLength) { + setValues(setting, matchedIndex, matchedLength); + } + + public void setValues(BooleanSetting setting, int matchedIndex, int matchedLength) { + this.setting = setting; + this.matchedIndex = matchedIndex; + this.matchedLength = matchedLength; + } + + /** + * A null value if the group has no setting, + * or if no match is returned from {@link FilterGroupList#check(Object)}. + */ + public BooleanSetting getSetting() { + return setting; + } + + public boolean isFiltered() { + return matchedIndex >= 0; + } + + /** + * Matched index of first pattern that matched, or -1 if nothing matched. + */ + public int getMatchedIndex() { + return matchedIndex; + } + + /** + * Length of the matched filter pattern. + */ + public int getMatchedLength() { + return matchedLength; + } + } + + protected final BooleanSetting setting; + protected final T[] filters; + + /** + * Initialize a new filter group. + * + * @param setting The associated setting. + * @param filters The filters. + */ + @SafeVarargs + public FilterGroup(final BooleanSetting setting, final T... filters) { + this.setting = setting; + this.filters = filters; + if (filters.length == 0) { + throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)"); + } + } + + public boolean isEnabled() { + return setting == null || setting.get(); + } + + /** + * @return If {@link FilterGroupList} should include this group when searching. + * By default, all filters are included except non enabled settings that require reboot. + */ + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + public boolean includeInSearch() { + return isEnabled() || !setting.rebootApp; + } + + @NonNull + @Override + public String toString() { + return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting); + } + + public abstract FilterGroupResult check(final T stack); + + + public static class StringFilterGroup extends FilterGroup { + + public StringFilterGroup(final BooleanSetting setting, final String... filters) { + super(setting, filters); + } + + @Override + public FilterGroupResult check(final String string) { + int matchedIndex = -1; + int matchedLength = 0; + if (isEnabled()) { + for (String pattern : filters) { + if (!string.isEmpty()) { + final int indexOf = string.indexOf(pattern); + if (indexOf >= 0) { + matchedIndex = indexOf; + matchedLength = pattern.length(); + break; + } + } + } + } + return new FilterGroupResult(setting, matchedIndex, matchedLength); + } + } + + /** + * If you have more than 1 filter patterns, then all instances of + * this class should filtered using {@link FilterGroupList.ByteArrayFilterGroupList#check(byte[])}, + * which uses a prefix tree to give better performance. + */ + public static class ByteArrayFilterGroup extends FilterGroup { + + private volatile int[][] failurePatterns; + + // Modified implementation from https://stackoverflow.com/a/1507813 + private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) { + // Finds the first occurrence of the pattern in the byte array using + // KMP matching algorithm. + int patternLength = pattern.length; + for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) { + while (j > 0 && pattern[j] != data[i]) { + j = failure[j - 1]; + } + if (pattern[j] == data[i]) { + j++; + } + if (j == patternLength) { + return i - patternLength + 1; + } + } + return -1; + } + + private static int[] createFailurePattern(byte[] pattern) { + // Computes the failure function using a boot-strapping process, + // where the pattern is matched against itself. + final int patternLength = pattern.length; + final int[] failure = new int[patternLength]; + + for (int i = 1, j = 0; i < patternLength; i++) { + while (j > 0 && pattern[j] != pattern[i]) { + j = failure[j - 1]; + } + if (pattern[j] == pattern[i]) { + j++; + } + failure[i] = j; + } + return failure; + } + + public ByteArrayFilterGroup(BooleanSetting setting, byte[]... filters) { + super(setting, filters); + } + + /** + * Converts the Strings into byte arrays. Used to search for text in binary data. + */ + public ByteArrayFilterGroup(BooleanSetting setting, String... filters) { + super(setting, ByteTrieSearch.convertStringsToBytes(filters)); + } + + private synchronized void buildFailurePatterns() { + if (failurePatterns != null) return; // Thread race and another thread already initialized the search. + Logger.printDebug(() -> "Building failure array for: " + this); + int[][] failurePatterns = new int[filters.length][]; + int i = 0; + for (byte[] pattern : filters) { + failurePatterns[i++] = createFailurePattern(pattern); + } + this.failurePatterns = failurePatterns; // Must set after initialization finishes. + } + + @Override + public FilterGroupResult check(final byte[] bytes) { + int matchedLength = 0; + int matchedIndex = -1; + if (isEnabled()) { + int[][] failures = failurePatterns; + if (failures == null) { + buildFailurePatterns(); // Lazy load. + failures = failurePatterns; + } + for (int i = 0, length = filters.length; i < length; i++) { + byte[] filter = filters[i]; + matchedIndex = indexOf(bytes, filter, failures[i]); + if (matchedIndex >= 0) { + matchedLength = filter.length; + break; + } + } + } + return new FilterGroupResult(setting, matchedIndex, matchedLength); + } + } +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/FilterGroupList.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroupList.java similarity index 56% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/FilterGroupList.java rename to extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroupList.java index f839436c0..e68276c7c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/FilterGroupList.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/FilterGroupList.java @@ -1,21 +1,22 @@ -package app.revanced.extension.youtube.patches.components; +package app.revanced.extension.shared.patches.litho; import androidx.annotation.NonNull; import java.util.*; -import java.util.function.Consumer; import app.revanced.extension.shared.ByteTrieSearch; import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.TrieSearch; +import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; +import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; -abstract class FilterGroupList> implements Iterable { +public abstract class FilterGroupList> implements Iterable { private final List filterGroups = new ArrayList<>(); private final TrieSearch search = createSearchGraph(); @SafeVarargs - protected final void addAll(final T... groups) { + public final void addAll(final T... groups) { filterGroups.addAll(Arrays.asList(groups)); for (T group : groups) { @@ -41,18 +42,7 @@ abstract class FilterGroupList> implements Iterable< return filterGroups.iterator(); } - @Override - public void forEach(@NonNull Consumer action) { - filterGroups.forEach(action); - } - - @NonNull - @Override - public Spliterator spliterator() { - return filterGroups.spliterator(); - } - - protected FilterGroup.FilterGroupResult check(V stack) { + public FilterGroup.FilterGroupResult check(V stack) { FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult(); search.matches(stack, result); return result; @@ -60,21 +50,21 @@ abstract class FilterGroupList> implements Iterable< } protected abstract TrieSearch createSearchGraph(); -} -final class StringFilterGroupList extends FilterGroupList { - protected StringTrieSearch createSearchGraph() { - return new StringTrieSearch(); + public static final class StringFilterGroupList extends FilterGroupList { + protected StringTrieSearch createSearchGraph() { + return new StringTrieSearch(); + } } -} -/** - * If searching for a single byte pattern, then it is slightly better to use - * {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster - * than a prefix tree to search for only 1 pattern. - */ -final class ByteArrayFilterGroupList extends FilterGroupList { - protected ByteTrieSearch createSearchGraph() { - return new ByteTrieSearch(); + /** + * If searching for a single byte pattern, then it is slightly better to use + * {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster + * than a prefix tree to search for only 1 pattern. + */ + public static final class ByteArrayFilterGroupList extends FilterGroupList { + protected ByteTrieSearch createSearchGraph() { + return new ByteTrieSearch(); + } } } \ No newline at end of file diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LithoFilterPatch.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java similarity index 97% rename from extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LithoFilterPatch.java rename to extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java index 2777dec11..5ff86428d 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LithoFilterPatch.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/patches/litho/LithoFilterPatch.java @@ -1,4 +1,4 @@ -package app.revanced.extension.youtube.patches.components; +package app.revanced.extension.shared.patches.litho; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -7,9 +7,11 @@ import java.nio.ByteBuffer; import java.util.List; import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.StringTrieSearch; -import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.shared.settings.BaseSettings; +import app.revanced.extension.shared.settings.YouTubeAndMusicSettings; + +import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; @SuppressWarnings("unused") public final class LithoFilterPatch { @@ -36,7 +38,7 @@ public final class LithoFilterPatch { builder.append(identifier); builder.append(" Path: "); builder.append(path); - if (Settings.DEBUG_PROTOBUFFER.get()) { + if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) { builder.append(" BufferStrings: "); findAsciiStrings(builder, buffer); } diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java new file mode 100644 index 000000000..67adab4ef --- /dev/null +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/settings/YouTubeAndMusicSettings.java @@ -0,0 +1,14 @@ +package app.revanced.extension.shared.settings; + +import static app.revanced.extension.shared.settings.Setting.parent; +import static java.lang.Boolean.FALSE; + +public class YouTubeAndMusicSettings extends BaseSettings { + // Custom filter + public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE); + public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER)); + + // Miscellaneous + public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false, + "revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG)); +} diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/AdsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/AdsFilter.java index 01f82f5cf..063fc53df 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/AdsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/AdsFilter.java @@ -11,6 +11,9 @@ import java.util.List; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.StringTrieSearch; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; +import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings("unused") @@ -153,8 +156,8 @@ public final class AdsFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(String identifier, String path, byte[] buffer, + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (matchedGroup == playerShoppingShelf) { return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered(); } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/AdvancedVideoQualityMenuFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/AdvancedVideoQualityMenuFilter.java index d2bbad012..9c6347c1e 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/AdvancedVideoQualityMenuFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/AdvancedVideoQualityMenuFilter.java @@ -1,5 +1,7 @@ package app.revanced.extension.youtube.patches.components; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.youtube.patches.playback.quality.AdvancedVideoQualityMenuPatch; import app.revanced.extension.youtube.settings.Settings; @@ -19,7 +21,7 @@ public final class AdvancedVideoQualityMenuFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, + public boolean isFiltered(String identifier, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { isVideoQualityMenuVisible = true; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ButtonsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ButtonsFilter.java index 3ca59ea0b..bc45b50fa 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ButtonsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ButtonsFilter.java @@ -1,9 +1,13 @@ package app.revanced.extension.youtube.patches.components; +import app.revanced.extension.shared.patches.litho.FilterGroupList.ByteArrayFilterGroupList; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.ByteArrayFilterGroup; +import app.revanced.extension.shared.patches.litho.FilterGroup.StringFilterGroup; import app.revanced.extension.youtube.settings.Settings; @SuppressWarnings("unused") -final class ButtonsFilter extends Filter { +public final class ButtonsFilter extends Filter { private static final String COMPACT_CHANNEL_BAR_PATH_PREFIX = "compact_channel_bar.e"; private static final String VIDEO_ACTION_BAR_PATH_PREFIX = "video_action_bar.e"; private static final String VIDEO_ACTION_BAR_PATH = "video_action_bar.e"; @@ -118,7 +122,7 @@ final class ButtonsFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, + public boolean isFiltered(String identifier, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (matchedGroup == likeSubscribeGlow) { return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX)) diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/CommentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/CommentsFilter.java index 2af8d1a90..1196db546 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/CommentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/CommentsFilter.java @@ -1,10 +1,12 @@ package app.revanced.extension.youtube.patches.components; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.PlayerType; @SuppressWarnings("unused") -final class CommentsFilter extends Filter { +public final class CommentsFilter extends Filter { private static final String COMMENT_COMPOSER_PATH = "comment_composer.e"; @@ -88,8 +90,8 @@ final class CommentsFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(String identifier, String path, byte[] buffer, + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (matchedGroup == chipBar) { // Playlist sort button uses same components and must only filter if the player is opened. return PlayerType.getCurrent().isMaximizedOrFullscreen() diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/DescriptionComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/DescriptionComponentsFilter.java index e7b1af55b..42c7d67ab 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/DescriptionComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/DescriptionComponentsFilter.java @@ -1,11 +1,14 @@ package app.revanced.extension.youtube.patches.components; import app.revanced.extension.shared.StringTrieSearch; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.*; +import app.revanced.extension.shared.patches.litho.FilterGroupList.*; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.PlayerType; @SuppressWarnings("unused") -final class DescriptionComponentsFilter extends Filter { +public final class DescriptionComponentsFilter extends Filter { private static final String INFOCARDS_SECTION_PATH = "infocards_section.e"; @@ -128,8 +131,8 @@ final class DescriptionComponentsFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(String identifier, String path, byte[] buffer, + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (matchedGroup == aiGeneratedVideoSummarySection || matchedGroup == hypePoints) { // Only hide if player is open, in case this component is used somewhere else. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/FilterGroup.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/FilterGroup.java deleted file mode 100644 index cf880b468..000000000 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/FilterGroup.java +++ /dev/null @@ -1,214 +0,0 @@ -package app.revanced.extension.youtube.patches.components; - -import androidx.annotation.NonNull; - -import app.revanced.extension.shared.Logger; -import app.revanced.extension.shared.settings.BooleanSetting; -import app.revanced.extension.shared.ByteTrieSearch; - -abstract class FilterGroup { - final static class FilterGroupResult { - private BooleanSetting setting; - private int matchedIndex; - private int matchedLength; - // In the future it might be useful to include which pattern matched, - // but for now that is not needed. - - FilterGroupResult() { - this(null, -1, 0); - } - - FilterGroupResult(BooleanSetting setting, int matchedIndex, int matchedLength) { - setValues(setting, matchedIndex, matchedLength); - } - - public void setValues(BooleanSetting setting, int matchedIndex, int matchedLength) { - this.setting = setting; - this.matchedIndex = matchedIndex; - this.matchedLength = matchedLength; - } - - /** - * A null value if the group has no setting, - * or if no match is returned from {@link FilterGroupList#check(Object)}. - */ - public BooleanSetting getSetting() { - return setting; - } - - public boolean isFiltered() { - return matchedIndex >= 0; - } - - /** - * Matched index of first pattern that matched, or -1 if nothing matched. - */ - public int getMatchedIndex() { - return matchedIndex; - } - - /** - * Length of the matched filter pattern. - */ - public int getMatchedLength() { - return matchedLength; - } - } - - protected final BooleanSetting setting; - protected final T[] filters; - - /** - * Initialize a new filter group. - * - * @param setting The associated setting. - * @param filters The filters. - */ - @SafeVarargs - public FilterGroup(final BooleanSetting setting, final T... filters) { - this.setting = setting; - this.filters = filters; - if (filters.length == 0) { - throw new IllegalArgumentException("Must use one or more filter patterns (zero specified)"); - } - } - - public boolean isEnabled() { - return setting == null || setting.get(); - } - - /** - * @return If {@link FilterGroupList} should include this group when searching. - * By default, all filters are included except non enabled settings that require reboot. - */ - @SuppressWarnings("BooleanMethodIsAlwaysInverted") - public boolean includeInSearch() { - return isEnabled() || !setting.rebootApp; - } - - @NonNull - @Override - public String toString() { - return getClass().getSimpleName() + ": " + (setting == null ? "(null setting)" : setting); - } - - public abstract FilterGroupResult check(final T stack); -} - -class StringFilterGroup extends FilterGroup { - - public StringFilterGroup(final BooleanSetting setting, final String... filters) { - super(setting, filters); - } - - @Override - public FilterGroupResult check(final String string) { - int matchedIndex = -1; - int matchedLength = 0; - if (isEnabled()) { - for (String pattern : filters) { - if (!string.isEmpty()) { - final int indexOf = string.indexOf(pattern); - if (indexOf >= 0) { - matchedIndex = indexOf; - matchedLength = pattern.length(); - break; - } - } - } - } - return new FilterGroupResult(setting, matchedIndex, matchedLength); - } -} - -/** - * If you have more than 1 filter patterns, then all instances of - * this class should filtered using {@link ByteArrayFilterGroupList#check(byte[])}, - * which uses a prefix tree to give better performance. - */ -class ByteArrayFilterGroup extends FilterGroup { - - private volatile int[][] failurePatterns; - - // Modified implementation from https://stackoverflow.com/a/1507813 - private static int indexOf(final byte[] data, final byte[] pattern, final int[] failure) { - // Finds the first occurrence of the pattern in the byte array using - // KMP matching algorithm. - int patternLength = pattern.length; - for (int i = 0, j = 0, dataLength = data.length; i < dataLength; i++) { - while (j > 0 && pattern[j] != data[i]) { - j = failure[j - 1]; - } - if (pattern[j] == data[i]) { - j++; - } - if (j == patternLength) { - return i - patternLength + 1; - } - } - return -1; - } - - private static int[] createFailurePattern(byte[] pattern) { - // Computes the failure function using a boot-strapping process, - // where the pattern is matched against itself. - final int patternLength = pattern.length; - final int[] failure = new int[patternLength]; - - for (int i = 1, j = 0; i < patternLength; i++) { - while (j > 0 && pattern[j] != pattern[i]) { - j = failure[j - 1]; - } - if (pattern[j] == pattern[i]) { - j++; - } - failure[i] = j; - } - return failure; - } - - public ByteArrayFilterGroup(BooleanSetting setting, byte[]... filters) { - super(setting, filters); - } - - /** - * Converts the Strings into byte arrays. Used to search for text in binary data. - */ - public ByteArrayFilterGroup(BooleanSetting setting, String... filters) { - super(setting, ByteTrieSearch.convertStringsToBytes(filters)); - } - - private synchronized void buildFailurePatterns() { - if (failurePatterns != null) return; // Thread race and another thread already initialized the search. - Logger.printDebug(() -> "Building failure array for: " + this); - int[][] failurePatterns = new int[filters.length][]; - int i = 0; - for (byte[] pattern : filters) { - failurePatterns[i++] = createFailurePattern(pattern); - } - this.failurePatterns = failurePatterns; // Must set after initialization finishes. - } - - @Override - public FilterGroupResult check(final byte[] bytes) { - int matchedLength = 0; - int matchedIndex = -1; - if (isEnabled()) { - int[][] failures = failurePatterns; - if (failures == null) { - buildFailurePatterns(); // Lazy load. - failures = failurePatterns; - } - for (int i = 0, length = filters.length; i < length; i++) { - byte[] filter = filters[i]; - matchedIndex = indexOf(bytes, filter, failures[i]); - if (matchedIndex >= 0) { - matchedLength = filter.length; - break; - } - } - } - return new FilterGroupResult(setting, matchedIndex, matchedLength); - } -} - diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/HideInfoCardsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/HideInfoCardsFilter.java index ca88f834f..dc9cf8e56 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/HideInfoCardsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/HideInfoCardsFilter.java @@ -1,6 +1,8 @@ package app.revanced.extension.youtube.patches.components; import app.revanced.extension.youtube.settings.Settings; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.*; @SuppressWarnings("unused") public final class HideInfoCardsFilter extends Filter { diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/KeywordContentFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/KeywordContentFilter.java index 8f9d99b6e..ad9d20e4c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/KeywordContentFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/KeywordContentFilter.java @@ -17,6 +17,8 @@ import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.ByteTrieSearch; import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.TrieSearch; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.PlayerType; @@ -41,7 +43,7 @@ import app.revanced.extension.youtube.shared.PlayerType; * - When using whole word syntax, some keywords may need additional pluralized variations. */ @SuppressWarnings("unused") -final class KeywordContentFilter extends Filter { +public final class KeywordContentFilter extends Filter { /** * Strings found in the buffer for every videos. Full strings should be specified. @@ -554,8 +556,8 @@ final class KeywordContentFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, - StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { + public boolean isFiltered(String identifier, String path, byte[] buffer, + StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (contentIndex != 0 && matchedGroup == startsWithFilter) { return false; } diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LayoutComponentsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LayoutComponentsFilter.java index 4c56466b2..7d2a20292 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LayoutComponentsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/LayoutComponentsFilter.java @@ -14,6 +14,9 @@ import androidx.annotation.Nullable; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.StringTrieSearch; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.*; +import app.revanced.extension.shared.patches.litho.FilterGroupList.*; import app.revanced.extension.youtube.patches.ChangeHeaderPatch; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.NavigationBar; @@ -342,7 +345,7 @@ public final class LayoutComponentsFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, + public boolean isFiltered(String identifier, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { // This identifier is used not only in players but also in search results: // https://github.com/ReVanced/revanced-patches/issues/3245 diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilter.java index d33dae8f2..0dc860fbd 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/PlaybackSpeedMenuFilter.java @@ -1,5 +1,7 @@ package app.revanced.extension.youtube.patches.components; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.*; import app.revanced.extension.youtube.patches.playback.speed.CustomPlaybackSpeedPatch; import app.revanced.extension.youtube.settings.Settings; @@ -36,7 +38,7 @@ public final class PlaybackSpeedMenuFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, + public boolean isFiltered(String identifier, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (matchedGroup == oldPlaybackMenuGroup) { isOldPlaybackSpeedMenuVisible = true; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/PlayerFlyoutMenuItemsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/PlayerFlyoutMenuItemsFilter.java index eae4eacfc..1e23a4db5 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/PlayerFlyoutMenuItemsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/PlayerFlyoutMenuItemsFilter.java @@ -3,13 +3,16 @@ package app.revanced.extension.youtube.patches.components; import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.*; +import app.revanced.extension.shared.patches.litho.FilterGroupList.*; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.ShortsPlayerState; import java.util.List; @SuppressWarnings("unused") -public class PlayerFlyoutMenuItemsFilter extends Filter { +public final class PlayerFlyoutMenuItemsFilter extends Filter { public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability { @Override @@ -94,7 +97,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, + public boolean isFiltered(String identifier, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (matchedGroup == videoQualityMenuFooter) { return true; diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilter.java index 58e3af5e6..d10efc5c0 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ReturnYouTubeDislikeFilter.java @@ -13,6 +13,9 @@ import app.revanced.extension.youtube.patches.VideoInformation; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.TrieSearch; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.*; +import app.revanced.extension.shared.patches.litho.FilterGroupList.*; /** * Searches for video id's in the proto buffer of Shorts dislike. @@ -84,13 +87,13 @@ public final class ReturnYouTubeDislikeFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, + public boolean isFiltered(String identifier, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) { return false; } - FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(buffer); + FilterGroupResult result = videoIdFilterGroup.check(buffer); if (result.isFiltered()) { String matchedVideoId = findVideoId(buffer); // Matched video will be null if in incognito mode. diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java index f2dea7af4..31b3470c5 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/components/ShortsFilter.java @@ -11,6 +11,9 @@ import java.util.Arrays; import java.util.List; import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.patches.litho.Filter; +import app.revanced.extension.shared.patches.litho.FilterGroup.*; +import app.revanced.extension.shared.patches.litho.FilterGroupList.*; import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.PlayerType; @@ -339,7 +342,7 @@ public final class ShortsFilter extends Filter { } @Override - boolean isFiltered(String identifier, String path, byte[] buffer, + public boolean isFiltered(String identifier, String path, byte[] buffer, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { if (contentType == FilterContentType.PATH) { if (matchedGroup == subscribeButton || matchedGroup == joinButton diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java index 21f3184b5..f8c13b83c 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/settings/Settings.java @@ -32,6 +32,7 @@ import android.graphics.Color; import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Utils; +import app.revanced.extension.shared.settings.YouTubeAndMusicSettings; import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.EnumSetting; @@ -49,7 +50,7 @@ import app.revanced.extension.youtube.patches.MiniplayerPatch; import app.revanced.extension.youtube.sponsorblock.SponsorBlockSettings; import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle; -public class Settings extends BaseSettings { +public class Settings extends YouTubeAndMusicSettings { // Video public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE); public static final BooleanSetting DISABLE_HDR_VIDEO = new BooleanSetting("revanced_disable_hdr_video", FALSE); @@ -274,11 +275,6 @@ public class Settings extends BaseSettings { public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true, new ChangeStartPageTypeAvailability()); public static final StringSetting SPOOF_APP_VERSION_TARGET = new StringSetting("revanced_spoof_app_version_target", "19.01.34", true, parent(SPOOF_APP_VERSION)); - - // Custom filter - public static final BooleanSetting CUSTOM_FILTER = new BooleanSetting("revanced_custom_filter", FALSE); - public static final StringSetting CUSTOM_FILTER_STRINGS = new StringSetting("revanced_custom_filter_strings", "", true, parent(CUSTOM_FILTER)); - // Navigation buttons public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true); public static final BooleanSetting HIDE_CREATE_BUTTON = new BooleanSetting("revanced_hide_create_button", TRUE, true); @@ -368,8 +364,6 @@ public class Settings extends BaseSettings { public static final EnumSetting SPOOF_VIDEO_STREAMS_CLIENT_TYPE = new EnumSetting<>("revanced_spoof_video_streams_client_type", ClientType.ANDROID_VR_1_43_32, true, parent(SPOOF_VIDEO_STREAMS)); public static final BooleanSetting SPOOF_VIDEO_STREAMS_AV1 = new BooleanSetting("revanced_spoof_video_streams_av1", FALSE, true, "revanced_spoof_video_streams_av1_user_dialog_message", new SpoofClientAv1Availability()); - public static final BooleanSetting DEBUG_PROTOBUFFER = new BooleanSetting("revanced_debug_protobuffer", FALSE, false, - "revanced_debug_protobuffer_user_dialog_message", parent(BaseSettings.DEBUG)); // Swipe controls public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true); @@ -382,7 +376,7 @@ public class Settings extends BaseSettings { public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true, parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); public static final IntegerSetting SWIPE_VOLUME_SENSITIVITY = new IntegerSetting("revanced_swipe_volume_sensitivity", 1, true, parent(SWIPE_VOLUME)); - public static final EnumSetting SWIPE_OVERLAY_STYLE = new EnumSetting<>("revanced_swipe_overlay_style", SwipeOverlayStyle.HORIZONTAL,true, + public static final EnumSetting SWIPE_OVERLAY_STYLE = new EnumSetting<>("revanced_swipe_overlay_style", SwipeOverlayStyle.HORIZONTAL, true, parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 14, true, parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); @@ -411,7 +405,9 @@ public class Settings extends BaseSettings { // SponsorBlock public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE); - /** Do not use id setting directly. Instead use {@link SponsorBlockSettings}. */ + /** + * Do not use id setting directly. Instead use {@link SponsorBlockSettings}. + */ public static final StringSetting SB_PRIVATE_USER_ID = new StringSetting("sb_private_user_id_Do_Not_Share", "", parent(SB_ENABLED)); public static final IntegerSetting SB_CREATE_NEW_SEGMENT_STEP = new IntegerSetting("sb_create_new_segment_step", 150, parent(SB_ENABLED)); public static final BooleanSetting SB_VOTING_BUTTON = new BooleanSetting("sb_voting_button", FALSE, parent(SB_ENABLED)); @@ -460,7 +456,7 @@ public class Settings extends BaseSettings { public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFFFF", false, false); // Deprecated migrations - private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033"); + private static final StringSetting DEPRECATED_SEEKBAR_CUSTOM_COLOR_PRIMARY = new StringSetting("revanced_seekbar_custom_color_value", "#FF0033"); private static final FloatSetting DEPRECATED_SB_CATEGORY_SPONSOR_OPACITY = new FloatSetting("sb_sponsor_opacity", 0.8f, false, false); private static final FloatSetting DEPRECATED_SB_CATEGORY_SELF_PROMO_OPACITY = new FloatSetting("sb_selfpromo_opacity", 0.8f, false, false); @@ -512,7 +508,7 @@ public class Settings extends BaseSettings { // or is spoofing to a version the same or newer than this app. if (!SPOOF_APP_VERSION_TARGET.isSetToDefault() && (SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0 - || (Utils.getAppVersionName().compareTo(SPOOF_APP_VERSION_TARGET.get()) <= 0))) { + || (Utils.getAppVersionName().compareTo(SPOOF_APP_VERSION_TARGET.get()) <= 0))) { Logger.printInfo(() -> "Resetting spoof app version"); SPOOF_APP_VERSION_TARGET.resetToDefault(); SPOOF_APP_VERSION.resetToDefault(); diff --git a/patches/api/patches.api b/patches/api/patches.api index abe4c4027..d1267e26c 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -472,6 +472,10 @@ public final class app/revanced/patches/music/layout/compactheader/HideCategoryB public static final fun getHideCategoryBar ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatchKt { + public static final fun getHideLayoutComponentsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColorKt { public static final fun getChangeMiniplayerColor ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -521,6 +525,10 @@ public final class app/revanced/patches/music/misc/gms/GmsCoreSupportPatchKt { public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/music/misc/litho/filter/LithoFilterPatchKt { + public static final fun getLithoFilterPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/music/misc/privacy/SanitizeSharingLinksPatchKt { public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -932,6 +940,12 @@ public final class app/revanced/patches/shared/misc/hex/Replacement { public final fun getReplacementBytesPadded ()[B } +public final class app/revanced/patches/shared/misc/litho/filter/LithoFilterPatchKt { + public static final fun getAddLithoFilter ()Lkotlin/jvm/functions/Function1; + public static final fun lithoFilterPatch (Lkotlin/jvm/functions/Function1;Lapp/revanced/patcher/Fingerprint;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lapp/revanced/patcher/patch/BytecodePatch; + public static synthetic fun lithoFilterPatch$default (Lkotlin/jvm/functions/Function1;Lapp/revanced/patcher/Fingerprint;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/shared/misc/mapping/ResourceElement { public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; diff --git a/patches/src/main/kotlin/app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatch.kt new file mode 100644 index 000000000..e4da9aafe --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/layout/hide/general/HideLayoutComponentsPatch.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.music.layout.hide.general + +import app.revanced.patches.music.misc.litho.filter.lithoFilterPatch +import app.revanced.patches.music.misc.settings.settingsPatch +import app.revanced.patches.shared.layout.hide.general.hideLayoutComponentsPatch + +val hideLayoutComponentsPatch = hideLayoutComponentsPatch( + lithoFilterPatch = lithoFilterPatch, + settingsPatch = settingsPatch, + filterClasses = setOf("Lapp/revanced/extension/shared/patches/components/CustomFilter;"), + compatibleWithPackages = arrayOf("com.google.android.apps.youtube.music" to setOf("7.29.52", "8.10.52")) +) \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt index 76ee0e060..16e4ddd80 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/debugging/EnableDebuggingPatch.kt @@ -7,20 +7,15 @@ import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch @Suppress("unused") val enableDebuggingPatch = enableDebuggingPatch( - block = { - dependsOn( - sharedExtensionPatch, - settingsPatch, + sharedExtensionPatch = sharedExtensionPatch, + settingsPatch = settingsPatch, + compatibleWithPackages = arrayOf( + "com.google.android.apps.youtube.music" to setOf( + "7.29.52", + "8.10.52" ) - - compatibleWith( - "com.google.android.apps.youtube.music"( - "7.29.52", - "8.10.52" - ) - ) - }, + ), // String feature flag does not appear to be present with YT Music. hookStringFeatureFlag = false, - preferenceScreen = PreferenceScreen.MISC + preferenceScreen = PreferenceScreen.MISC, ) diff --git a/patches/src/main/kotlin/app/revanced/patches/music/misc/litho/filter/LithoFilterPatch.kt b/patches/src/main/kotlin/app/revanced/patches/music/misc/litho/filter/LithoFilterPatch.kt new file mode 100644 index 000000000..fb9257999 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/music/misc/litho/filter/LithoFilterPatch.kt @@ -0,0 +1,17 @@ +package app.revanced.patches.music.misc.litho.filter + +import app.revanced.patches.music.misc.extension.sharedExtensionPatch +import app.revanced.patches.music.shared.conversionContextFingerprintToString +import app.revanced.patches.shared.misc.litho.filter.lithoFilterPatch +import app.revanced.util.indexOfFirstInstructionOrThrow +import com.android.tools.smali.dexlib2.Opcode + +val lithoFilterPatch = lithoFilterPatch( + componentCreateInsertionIndex = { + // No supported version clobbers p2 so we can just do our things before the return instruction. + indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT) + }, + conversionContextFingerprintToString = conversionContextFingerprintToString, +) { + dependsOn(sharedExtensionPatch) +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/music/shared/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/music/shared/Fingerprints.kt index d6c79197d..f6e7da942 100644 --- a/patches/src/main/kotlin/app/revanced/patches/music/shared/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/music/shared/Fingerprints.kt @@ -11,3 +11,19 @@ internal val mainActivityOnCreateFingerprint = fingerprint { method.name == "onCreate" && classDef.type == YOUTUBE_MUSIC_MAIN_ACTIVITY_CLASS_TYPE } } + +internal val conversionContextFingerprintToString = fingerprint { + parameters() + strings( + "ConversionContext{containerInternal=", + ", gridColumnCount=", + ", gridColumnIndex=", + ", templateLoggerFactory=", + ", rootDisposableContainer=", + ", elementId=", + ", identifierProperty=" + ) + custom { method, _ -> + method.name == "toString" + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/layout/hide/general/HideLayoutComponentsPatch.kt new file mode 100644 index 000000000..53269677a --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/layout/hide/general/HideLayoutComponentsPatch.kt @@ -0,0 +1,55 @@ +package app.revanced.patches.shared.layout.hide.general + +import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.music.misc.settings.PreferenceScreen +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter +import app.revanced.patches.shared.misc.settings.preference.InputType +import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference +import app.revanced.patches.shared.misc.settings.preference.SwitchPreference +import app.revanced.patches.shared.misc.settings.preference.TextPreference + +internal fun hideLayoutComponentsPatch( + lithoFilterPatch: Patch<*>, + settingsPatch: Patch<*>, + additionalDependencies: Set> = emptySet(), + filterClasses: Set, + vararg compatibleWithPackages: Pair?>, + executeBlock: BytecodePatchContext.() -> Unit = {}, +) = bytecodePatch( + name = "Hide layout components", + description = "Adds options to hide general layout components.", +) { + dependsOn( + lithoFilterPatch, + settingsPatch, + *additionalDependencies.toTypedArray(), + addResourcesPatch, + ) + + compatibleWith(packages = compatibleWithPackages) + + execute { + addResources("shared", "layout.hide.general.hideLayoutComponentsPatch") + + PreferenceScreen.GENERAL.addPreferences( + PreferenceScreenPreference( + key = "revanced_custom_filter_screen", + sorting = PreferenceScreenPreference.Sorting.UNSORTED, + preferences = setOf( + SwitchPreference("revanced_custom_filter"), + TextPreference("revanced_custom_filter_strings", inputType = InputType.TEXT_MULTI_LINE), + ), + ), + ) + + filterClasses.forEach { className -> + addLithoFilter(className) + } + + executeBlock() + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/EnableDebuggingPatch.kt index 5680cd954..dd00a6eba 100644 --- a/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/debugging/EnableDebuggingPatch.kt @@ -2,23 +2,14 @@ package app.revanced.patches.shared.misc.debugging import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.getInstruction -import app.revanced.patcher.patch.BytecodePatchBuilder -import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.patch.Patch import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch -import app.revanced.patches.shared.misc.settings.preference.BasePreference -import app.revanced.patches.shared.misc.settings.preference.BasePreferenceScreen -import app.revanced.patches.shared.misc.settings.preference.NonInteractivePreference -import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference +import app.revanced.patches.shared.misc.settings.preference.* import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting -import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.util.ResourceGroup -import app.revanced.util.copyResources -import app.revanced.util.findInstructionIndicesReversedOrThrow -import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import app.revanced.util.* import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -29,23 +20,27 @@ private const val EXTENSION_CLASS_DESCRIPTOR = * Patch shared with YouTube and YT Music. */ internal fun enableDebuggingPatch( - block: BytecodePatchBuilder.() -> Unit = {}, - executeBlock: BytecodePatchContext.() -> Unit = {}, + sharedExtensionPatch: Patch<*>, + settingsPatch: Patch<*>, + vararg compatibleWithPackages: Pair>, hookStringFeatureFlag: Boolean, preferenceScreen: BasePreferenceScreen.Screen, - additionalDebugPreferences: List = emptyList() ) = bytecodePatch( name = "Enable debugging", description = "Adds options for debugging and exporting ReVanced logs to the clipboard.", ) { + compatibleWith(packages = compatibleWithPackages) dependsOn( + sharedExtensionPatch, + settingsPatch, addResourcesPatch, resourcePatch { execute { copyResources( "settings", - ResourceGroup("drawable", + ResourceGroup( + "drawable", // Action buttons. "revanced_settings_copy_all.xml", "revanced_settings_deselect_all.xml", @@ -61,38 +56,29 @@ internal fun enableDebuggingPatch( } ) - block() execute { - executeBlock() - addResources("shared", "misc.debugging.enableDebuggingPatch") - val preferences = mutableSetOf( + val preferences = setOf( SwitchPreference("revanced_debug"), - ) - - preferences.addAll(additionalDebugPreferences) - - preferences.addAll( - listOf( - SwitchPreference("revanced_debug_stacktrace"), - SwitchPreference("revanced_debug_toast_on_error"), - NonInteractivePreference( - "revanced_debug_export_logs_to_clipboard", - tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference", - selectable = true - ), - NonInteractivePreference( - "revanced_debug_logs_clear_buffer", - tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference", - selectable = true - ), - NonInteractivePreference( - "revanced_debug_feature_flags_manager", - tag = "app.revanced.extension.shared.settings.preference.FeatureFlagsManagerPreference", - selectable = true - ) + SwitchPreference("revanced_debug_protobuffer"), + SwitchPreference("revanced_debug_stacktrace"), + SwitchPreference("revanced_debug_toast_on_error"), + NonInteractivePreference( + "revanced_debug_export_logs_to_clipboard", + tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference", + selectable = true + ), + NonInteractivePreference( + "revanced_debug_logs_clear_buffer", + tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference", + selectable = true + ), + NonInteractivePreference( + "revanced_debug_feature_flags_manager", + tag = "app.revanced.extension.shared.settings.preference.FeatureFlagsManagerPreference", + selectable = true ) ) diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt new file mode 100644 index 000000000..d405a5853 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/Fingerprints.kt @@ -0,0 +1,58 @@ +package app.revanced.patches.shared.misc.litho.filter + +import app.revanced.patcher.fingerprint +import app.revanced.util.containsLiteralInstruction +import app.revanced.util.literal +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +internal val lithoFilterFingerprint = fingerprint { + accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) + custom { _, classDef -> + classDef.endsWith("/LithoFilterPatch;") + } +} + +/** + * Matches a method that use the protobuf of our component. + */ +internal val protobufBufferReferenceFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) + returns("V") + parameters("I", "Ljava/nio/ByteBuffer;") + opcodes( + Opcode.IPUT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.SUB_INT_2ADDR, + ) +} + +internal val componentContextParserFingerprint = fingerprint { + strings("Number of bits must be positive") +} + +internal val emptyComponentFingerprint = fingerprint { + accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR) + parameters() + strings("EmptyComponent") + custom { _, classDef -> + classDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1 + } +} + +internal val componentCreateFingerprint = fingerprint { + strings( + "Element missing correct type extension", + "Element missing type" + ) +} + +internal val lithoThreadExecutorFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) + parameters("I", "I", "I") + custom { method, classDef -> + classDef.superclass == "Ljava/util/concurrent/ThreadPoolExecutor;" && + method.containsLiteralInstruction(1L) // 1L = default thread timeout. + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/LithoFilterPatch.kt b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/LithoFilterPatch.kt new file mode 100644 index 000000000..ee91383b0 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/shared/misc/litho/filter/LithoFilterPatch.kt @@ -0,0 +1,211 @@ +@file:Suppress("SpellCheckingInspection") + +package app.revanced.patches.shared.misc.litho.filter + +import app.revanced.patcher.Fingerprint +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.InstructionExtensions.removeInstructions +import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction +import app.revanced.patcher.patch.BytecodePatchBuilder +import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.shared.misc.extension.sharedExtensionPatch +import app.revanced.util.addInstructionsAtControlFlowLabel +import app.revanced.util.findFreeRegister +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionReversedOrThrow +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference + +/** + * Used to add a hook point to the extension stub. + */ +lateinit var addLithoFilter: (String) -> Unit + private set + +/** + * Counts the number of filters added to the static field array. + */ +private var filterCount = 0 + +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/shared/patches/litho/LithoFilterPatch;" + +/** + * A patch that allows to filter Litho components based on their identifier or path. + * + * @param componentCreateInsertionIndex The index to insert the filtering code in the component create method. + * @param conversionContextFingerprintToString The fingerprint of the conversion context to string method. + * @param executeBlock The additional execution block of the patch. + * @param block The additional block to build the patch. + */ +internal fun lithoFilterPatch( + componentCreateInsertionIndex: Method.() -> Int, + conversionContextFingerprintToString: Fingerprint, + executeBlock: BytecodePatchContext.() -> Unit = {}, + block: BytecodePatchBuilder.() -> Unit = {}, +) = bytecodePatch( + description = "Hooks the method which parses the bytes into a ComponentContext to filter components.", +) { + dependsOn( + sharedExtensionPatch(), + ) + + /** + * The following patch inserts a hook into the method that parses the bytes into a ComponentContext. + * This method contains a StringBuilder object that represents the pathBuilder of the component. + * The pathBuilder is used to filter components by their path. + * + * Additionally, the method contains a reference to the component's identifier. + * The identifier is used to filter components by their identifier. + * + * The protobuf buffer is passed along from a different injection point before the filtering occurs. + * The buffer is a large byte array that represents the component tree. + * This byte array is searched for strings that indicate the current component. + * + * All modifications done here must allow all the original code to still execute + * even when filtering, otherwise memory leaks or poor app performance may occur. + * + * The following pseudocode shows how this patch works: + * + * class SomeOtherClass { + * // Called before ComponentContextParser.parseComponent() method. + * public void someOtherMethod(ByteBuffer byteBuffer) { + * ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch. + * ... + * } + * } + * + * class CreateComponentClass { + * public Component createComponent() { + * ... + * + * if (extensionClass.shouldFilter(identifier, path)) { // Inserted by this patch. + * return emptyComponent; + * } + * return originalUnpatchedComponent; // Original code. + * } + * } + */ + execute { + // Remove dummy filter from extenion static field + // and add the filters included during patching. + lithoFilterFingerprint.method.apply { + removeInstructions(2, 4) // Remove dummy filter. + + addLithoFilter = { classDescriptor -> + addInstructions( + 2, + """ + new-instance v1, $classDescriptor + invoke-direct { v1 }, $classDescriptor->()V + const/16 v2, ${filterCount++} + aput-object v1, v0, v2 + """, + ) + } + } + + // Add an interceptor to steal the protobuf of our component. + protobufBufferReferenceFingerprint.method.addInstruction( + 0, + "invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V", + ) + + + // Hook the method that parses bytes into a ComponentContext. + // Allow the method to run to completion, and override the + // return value with an empty component if it should be filtered. + // It is important to allow the original code to always run to completion, + // otherwise high memory usage and poor app performance can occur. + + // Find the identifier/path fields of the conversion context. + val conversionContextIdentifierField = componentContextParserFingerprint.let { + // Identifier field is loaded just before the string declaration. + val index = it.method.indexOfFirstInstructionReversedOrThrow( + it.stringMatches!!.first().index + ) { + // Our instruction reads a String from a field of the ConversionContext class. + val reference = getReference() + reference?.definingClass == conversionContextFingerprintToString.originalClassDef.type + && reference.type == "Ljava/lang/String;" + } + + it.method.getInstruction(index).getReference()!! + } + + val conversionContextPathBuilderField = conversionContextFingerprintToString.originalClassDef + .fields.single { field -> field.type == "Ljava/lang/StringBuilder;" } + + // Find class and methods to create an empty component. + val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.single { + // The only static method in the class. + method -> + AccessFlags.STATIC.isSet(method.accessFlags) + } + + val emptyComponentField = classBy { + // Only one field that matches. + it.type == builderMethodDescriptor.returnType + }!!.immutableClass.fields.single() + + // Match all component creations methods + componentCreateFingerprint.method.apply { + val insertIndex = componentCreateInsertionIndex() + val freeRegister = findFreeRegister(insertIndex) + val identifierRegister = findFreeRegister(insertIndex, freeRegister) + val pathRegister = findFreeRegister(insertIndex, freeRegister, identifierRegister) + + addInstructionsAtControlFlowLabel( + insertIndex, + """ + move-object/from16 v$freeRegister, p2 # ConversionContext parameter + check-cast v$freeRegister, ${conversionContextFingerprintToString.originalClassDef.type} # Check we got the actual ConversionContext + + # Get identifier and path from ConversionContext + iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField + iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField + + # Check if the component should be filtered. + invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->isFiltered(Ljava/lang/String;Ljava/lang/StringBuilder;)Z + move-result v$freeRegister + if-eqz v$freeRegister, :unfiltered + + # Return an empty component + move-object/from16 v$freeRegister, p1 + invoke-static { v$freeRegister }, $builderMethodDescriptor + move-result-object v$freeRegister + iget-object v$freeRegister, v$freeRegister, $emptyComponentField + return-object v$freeRegister + + :unfiltered + nop + """ + ) + } + + // TODO: Check if needed in music + // Change Litho thread executor to 1 thread to fix layout issue in unpatched YouTube. + lithoThreadExecutorFingerprint.method.addInstructions( + 0, + """ + invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorCorePoolSize(I)I + move-result p1 + invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorMaxThreads(I)I + move-result p2 + """ + ) + + executeBlock() + } + + finalize { + // Save the number of filters added. + lithoFilterFingerprint.method.replaceInstruction(0, "const/16 v0, $filterCount") + } + + block() +} diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt index d71664b80..436a5d1d0 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/ad/general/HideAdsPatch.kt @@ -7,13 +7,13 @@ import app.revanced.patcher.patch.resourcePatch import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.fix.verticalscroll.verticalScrollPatch +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.ad.getpremium.hideGetPremiumPatch import app.revanced.patches.youtube.misc.fix.backtoexitgesture.fixBackToExitGesturePatch -import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt index 49fa65dfe..f48a3b3c2 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/buttons/action/HideButtonsPatch.kt @@ -6,7 +6,7 @@ import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt index 07eecb83f..49e38337b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/general/HideLayoutComponentsPatch.kt @@ -9,34 +9,27 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction -import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patches.all.misc.resources.addResources -import app.revanced.patches.all.misc.resources.addResourcesPatch +import app.revanced.patches.shared.layout.hide.general.hideLayoutComponentsPatch import app.revanced.patches.shared.misc.mapping.get import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.settings.preference.* -import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch import app.revanced.patches.youtube.misc.playservice.is_20_07_or_greater import app.revanced.patches.youtube.misc.playservice.is_20_09_or_greater import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch -import app.revanced.util.findFreeRegister -import app.revanced.util.findInstructionIndicesReversedOrThrow -import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.indexOfFirstLiteralInstructionOrThrow +import app.revanced.util.* import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.iface.Method import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction import com.android.tools.smali.dexlib2.iface.reference.MethodReference -import app.revanced.util.indexOfFirstInstructionReversedOrThrow var expandButtonDownId = -1L private set @@ -108,184 +101,170 @@ private const val DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME = private const val COMMENTS_FILTER_CLASS_NAME = "Lapp/revanced/extension/youtube/patches/components/CommentsFilter;" private const val CUSTOM_FILTER_CLASS_NAME = - "Lapp/revanced/extension/youtube/patches/components/CustomFilter;" + "Lapp/revanced/extension/shared/patches/components/CustomFilter;" private const val KEYWORD_FILTER_CLASS_NAME = "Lapp/revanced/extension/youtube/patches/components/KeywordContentFilter;" -val hideLayoutComponentsPatch = bytecodePatch( - name = "Hide layout components", - description = "Adds options to hide general layout components.", -) { - dependsOn( - lithoFilterPatch, - settingsPatch, - addResourcesPatch, +val hideLayoutComponentsPatch = hideLayoutComponentsPatch( + lithoFilterPatch = lithoFilterPatch, + settingsPatch = settingsPatch, + additionalDependencies = setOf( hideLayoutComponentsResourcePatch, navigationBarHookPatch, - ) - - compatibleWith( - "com.google.android.youtube"( + ), + filterClasses = setOf( + LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR, + DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME, + COMMENTS_FILTER_CLASS_NAME, + KEYWORD_FILTER_CLASS_NAME, + CUSTOM_FILTER_CLASS_NAME + ), + compatibleWithPackages = arrayOf( + "com.google.android.youtube" to setOf( "19.34.42", "20.07.39", "20.13.41", "20.14.43", ) ) +) { + addResources("youtube", "layout.hide.general.hideLayoutComponentsPatch") - execute { - addResources("youtube", "layout.hide.general.hideLayoutComponentsPatch") + PreferenceScreen.PLAYER.addPreferences( + PreferenceScreenPreference( + key = "revanced_hide_description_components_screen", + preferences = setOf( + SwitchPreference("revanced_hide_ai_generated_video_summary_section"), + SwitchPreference("revanced_hide_ask_section"), + SwitchPreference("revanced_hide_attributes_section"), + SwitchPreference("revanced_hide_chapters_section"), + SwitchPreference("revanced_hide_featured_links_section"), + SwitchPreference("revanced_hide_featured_videos_section"), + SwitchPreference("revanced_hide_info_cards_section"), + SwitchPreference("revanced_hide_how_this_was_made_section"), + SwitchPreference("revanced_hide_hype_points"), + SwitchPreference("revanced_hide_key_concepts_section"), + SwitchPreference("revanced_hide_podcast_section"), + SwitchPreference("revanced_hide_subscribe_button"), + SwitchPreference("revanced_hide_transcript_section"), + ), + ), + PreferenceScreenPreference( + "revanced_comments_screen", + preferences = setOf( + SwitchPreference("revanced_hide_comments_ai_chat_summary"), + SwitchPreference("revanced_hide_comments_ai_summary"), + SwitchPreference("revanced_hide_comments_channel_guidelines"), + SwitchPreference("revanced_hide_comments_by_members_header"), + SwitchPreference("revanced_hide_comments_section"), + SwitchPreference("revanced_hide_comments_community_guidelines"), + SwitchPreference("revanced_hide_comments_create_a_short_button"), + SwitchPreference("revanced_hide_comments_emoji_and_timestamp_buttons"), + SwitchPreference("revanced_hide_comments_preview_comment"), + SwitchPreference("revanced_hide_comments_thanks_button"), + ), + sorting = PreferenceScreenPreference.Sorting.UNSORTED, + ), + SwitchPreference("revanced_hide_channel_bar"), + SwitchPreference("revanced_hide_channel_watermark"), + SwitchPreference("revanced_hide_crowdfunding_box"), + SwitchPreference("revanced_hide_emergency_box"), + SwitchPreference("revanced_hide_info_panels"), + SwitchPreference("revanced_hide_join_membership_button"), + SwitchPreference("revanced_hide_medical_panels"), + SwitchPreference("revanced_hide_quick_actions"), + SwitchPreference("revanced_hide_related_videos"), + SwitchPreference("revanced_hide_subscribers_community_guidelines"), + SwitchPreference("revanced_hide_timed_reactions"), + ) - PreferenceScreen.PLAYER.addPreferences( - PreferenceScreenPreference( - key = "revanced_hide_description_components_screen", - preferences = setOf( - SwitchPreference("revanced_hide_ai_generated_video_summary_section"), - SwitchPreference("revanced_hide_ask_section"), - SwitchPreference("revanced_hide_attributes_section"), - SwitchPreference("revanced_hide_chapters_section"), - SwitchPreference("revanced_hide_featured_links_section"), - SwitchPreference("revanced_hide_featured_videos_section"), - SwitchPreference("revanced_hide_info_cards_section"), - SwitchPreference("revanced_hide_how_this_was_made_section"), - SwitchPreference("revanced_hide_hype_points"), - SwitchPreference("revanced_hide_key_concepts_section"), - SwitchPreference("revanced_hide_podcast_section"), - SwitchPreference("revanced_hide_subscribe_button"), - SwitchPreference("revanced_hide_transcript_section"), + PreferenceScreen.FEED.addPreferences( + PreferenceScreenPreference( + key = "revanced_hide_keyword_content_screen", + sorting = PreferenceScreenPreference.Sorting.UNSORTED, + preferences = setOf( + SwitchPreference("revanced_hide_keyword_content_home"), + SwitchPreference("revanced_hide_keyword_content_subscriptions"), + SwitchPreference("revanced_hide_keyword_content_search"), + TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE), + NonInteractivePreference( + key = "revanced_hide_keyword_content_about", + tag = "app.revanced.extension.shared.settings.preference.BulletPointPreference" + ), + NonInteractivePreference( + key = "revanced_hide_keyword_content_about_whole_words", + tag = "app.revanced.extension.youtube.settings.preference.HtmlPreference", ), ), - PreferenceScreenPreference( - "revanced_comments_screen", - preferences = setOf( - SwitchPreference("revanced_hide_comments_ai_chat_summary"), - SwitchPreference("revanced_hide_comments_ai_summary"), - SwitchPreference("revanced_hide_comments_channel_guidelines"), - SwitchPreference("revanced_hide_comments_by_members_header"), - SwitchPreference("revanced_hide_comments_section"), - SwitchPreference("revanced_hide_comments_community_guidelines"), - SwitchPreference("revanced_hide_comments_create_a_short_button"), - SwitchPreference("revanced_hide_comments_emoji_and_timestamp_buttons"), - SwitchPreference("revanced_hide_comments_preview_comment"), - SwitchPreference("revanced_hide_comments_thanks_button"), - ), - sorting = PreferenceScreenPreference.Sorting.UNSORTED, + ), + PreferenceScreenPreference( + key = "revanced_hide_filter_bar_screen", + preferences = setOf( + SwitchPreference("revanced_hide_filter_bar_feed_in_feed"), + SwitchPreference("revanced_hide_filter_bar_feed_in_related_videos"), + SwitchPreference("revanced_hide_filter_bar_feed_in_search"), + SwitchPreference("revanced_hide_filter_bar_feed_in_history"), ), - SwitchPreference("revanced_hide_channel_bar"), - SwitchPreference("revanced_hide_channel_watermark"), - SwitchPreference("revanced_hide_crowdfunding_box"), - SwitchPreference("revanced_hide_emergency_box"), - SwitchPreference("revanced_hide_info_panels"), - SwitchPreference("revanced_hide_join_membership_button"), - SwitchPreference("revanced_hide_medical_panels"), - SwitchPreference("revanced_hide_quick_actions"), - SwitchPreference("revanced_hide_related_videos"), - SwitchPreference("revanced_hide_subscribers_community_guidelines"), - SwitchPreference("revanced_hide_timed_reactions"), - ) - - PreferenceScreen.FEED.addPreferences( - PreferenceScreenPreference( - key = "revanced_hide_keyword_content_screen", - sorting = PreferenceScreenPreference.Sorting.UNSORTED, - preferences = setOf( - SwitchPreference("revanced_hide_keyword_content_home"), - SwitchPreference("revanced_hide_keyword_content_subscriptions"), - SwitchPreference("revanced_hide_keyword_content_search"), - TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE), - NonInteractivePreference( - key = "revanced_hide_keyword_content_about", - tag = "app.revanced.extension.shared.settings.preference.BulletPointPreference" - ), - NonInteractivePreference( - key = "revanced_hide_keyword_content_about_whole_words", - tag = "app.revanced.extension.youtube.settings.preference.HtmlPreference", - ), - ), + ), + PreferenceScreenPreference( + key = "revanced_channel_screen", + preferences = setOf( + SwitchPreference("revanced_hide_community_button"), + SwitchPreference("revanced_hide_for_you_shelf"), + SwitchPreference("revanced_hide_join_button"), + SwitchPreference("revanced_hide_links_preview"), + SwitchPreference("revanced_hide_members_shelf"), + SwitchPreference("revanced_hide_store_button"), + SwitchPreference("revanced_hide_subscribe_button_in_channel_page"), ), - PreferenceScreenPreference( - key = "revanced_hide_filter_bar_screen", - preferences = setOf( - SwitchPreference("revanced_hide_filter_bar_feed_in_feed"), - SwitchPreference("revanced_hide_filter_bar_feed_in_related_videos"), - SwitchPreference("revanced_hide_filter_bar_feed_in_search"), - SwitchPreference("revanced_hide_filter_bar_feed_in_history"), - ), - ), - PreferenceScreenPreference( - key = "revanced_channel_screen", - preferences = setOf( - SwitchPreference("revanced_hide_community_button"), - SwitchPreference("revanced_hide_for_you_shelf"), - SwitchPreference("revanced_hide_join_button"), - SwitchPreference("revanced_hide_links_preview"), - SwitchPreference("revanced_hide_members_shelf"), - SwitchPreference("revanced_hide_store_button"), - SwitchPreference("revanced_hide_subscribe_button_in_channel_page"), - ), - ), - SwitchPreference("revanced_hide_album_cards"), - SwitchPreference("revanced_hide_artist_cards"), - SwitchPreference("revanced_hide_chips_shelf"), - SwitchPreference("revanced_hide_community_posts"), - SwitchPreference("revanced_hide_compact_banner"), - SwitchPreference("revanced_hide_expandable_card"), - SwitchPreference("revanced_hide_floating_microphone_button"), - SwitchPreference( - key = "revanced_hide_horizontal_shelves", - tag = "app.revanced.extension.shared.settings.preference.BulletPointSwitchPreference" - ), - SwitchPreference("revanced_hide_image_shelf"), - SwitchPreference("revanced_hide_latest_posts"), - SwitchPreference("revanced_hide_mix_playlists"), - SwitchPreference("revanced_hide_movies_section"), - SwitchPreference("revanced_hide_notify_me_button"), - SwitchPreference("revanced_hide_playables"), - SwitchPreference("revanced_hide_show_more_button"), - SwitchPreference("revanced_hide_surveys"), - SwitchPreference("revanced_hide_ticket_shelf"), - SwitchPreference("revanced_hide_upload_time"), - SwitchPreference("revanced_hide_video_recommendation_labels"), - SwitchPreference("revanced_hide_view_count"), - SwitchPreference("revanced_hide_visual_spacer"), - SwitchPreference("revanced_hide_doodles"), - ) + ), + SwitchPreference("revanced_hide_album_cards"), + SwitchPreference("revanced_hide_artist_cards"), + SwitchPreference("revanced_hide_chips_shelf"), + SwitchPreference("revanced_hide_community_posts"), + SwitchPreference("revanced_hide_compact_banner"), + SwitchPreference("revanced_hide_expandable_card"), + SwitchPreference("revanced_hide_floating_microphone_button"), + SwitchPreference( + key = "revanced_hide_horizontal_shelves", + tag = "app.revanced.extension.shared.settings.preference.BulletPointSwitchPreference" + ), + SwitchPreference("revanced_hide_image_shelf"), + SwitchPreference("revanced_hide_latest_posts"), + SwitchPreference("revanced_hide_mix_playlists"), + SwitchPreference("revanced_hide_movies_section"), + SwitchPreference("revanced_hide_notify_me_button"), + SwitchPreference("revanced_hide_playables"), + SwitchPreference("revanced_hide_show_more_button"), + SwitchPreference("revanced_hide_surveys"), + SwitchPreference("revanced_hide_ticket_shelf"), + SwitchPreference("revanced_hide_upload_time"), + SwitchPreference("revanced_hide_video_recommendation_labels"), + SwitchPreference("revanced_hide_view_count"), + SwitchPreference("revanced_hide_visual_spacer"), + SwitchPreference("revanced_hide_doodles"), + ) - PreferenceScreen.GENERAL_LAYOUT.addPreferences( - PreferenceScreenPreference( - key = "revanced_custom_filter_screen", - sorting = PreferenceScreenPreference.Sorting.UNSORTED, - preferences = setOf( - SwitchPreference("revanced_custom_filter"), - TextPreference("revanced_custom_filter_strings", inputType = InputType.TEXT_MULTI_LINE), - ), - ), - ) + // region Mix playlists - addLithoFilter(LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR) - addLithoFilter(DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME) - addLithoFilter(COMMENTS_FILTER_CLASS_NAME) - addLithoFilter(KEYWORD_FILTER_CLASS_NAME) - addLithoFilter(CUSTOM_FILTER_CLASS_NAME) + (if (is_20_09_or_greater) parseElementFromBufferFingerprint + else if (is_20_07_or_greater) parseElementFromBufferLegacy2007Fingerprint + else parseElementFromBufferLegacy1901Fingerprint).let { + it.method.apply { + val byteArrayParameter = "p3" + val startIndex = it.patternMatch!!.startIndex + val conversionContextRegister = getInstruction(startIndex).registerA + val returnEmptyComponentInstruction = instructions.last { it.opcode == Opcode.INVOKE_STATIC } + val returnEmptyComponentRegister = + (returnEmptyComponentInstruction as FiveRegisterInstruction).registerC + val insertIndex = startIndex + 1 + val freeRegister = + findFreeRegister(insertIndex, conversionContextRegister, returnEmptyComponentRegister) - // region Mix playlists - - (if (is_20_09_or_greater) parseElementFromBufferFingerprint - else if (is_20_07_or_greater) parseElementFromBufferLegacy2007Fingerprint - else parseElementFromBufferLegacy1901Fingerprint).let { - it.method.apply { - val byteArrayParameter = "p3" - val startIndex = it.patternMatch!!.startIndex - val conversionContextRegister = getInstruction(startIndex).registerA - val returnEmptyComponentInstruction = instructions.last { it.opcode == Opcode.INVOKE_STATIC } - val returnEmptyComponentRegister = (returnEmptyComponentInstruction as FiveRegisterInstruction).registerC - val insertIndex = startIndex + 1 - val freeRegister = findFreeRegister(insertIndex, conversionContextRegister, returnEmptyComponentRegister) - - addInstructionsWithLabels( - insertIndex, - """ + addInstructionsWithLabels( + insertIndex, + """ invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z move-result v$freeRegister if-eqz v$freeRegister, :show @@ -294,193 +273,192 @@ val hideLayoutComponentsPatch = bytecodePatch( :show nop """, - ExternalLabel("return_empty_component", returnEmptyComponentInstruction), - ) - } + ExternalLabel("return_empty_component", returnEmptyComponentInstruction), + ) } + } - // endregion + // endregion - // region Watermark (legacy code for old versions of YouTube) + // region Watermark (legacy code for old versions of YouTube) - showWatermarkFingerprint.match( - playerOverlayFingerprint.originalClassDef, - ).method.apply { - val index = implementation!!.instructions.size - 5 + showWatermarkFingerprint.match( + playerOverlayFingerprint.originalClassDef, + ).method.apply { + val index = implementation!!.instructions.size - 5 - removeInstruction(index) - addInstructions( - index, - """ + removeInstruction(index) + addInstructions( + index, + """ invoke-static {}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->showWatermark()Z move-result p2 """, - ) - } + ) + } - // endregion + // endregion - // region Show more button + // region Show more button - hideShowMoreButtonFingerprint.method.apply { - val moveRegisterIndex = hideShowMoreButtonFingerprint.patternMatch!!.endIndex - val viewRegister = getInstruction(moveRegisterIndex).registerA + hideShowMoreButtonFingerprint.method.apply { + val moveRegisterIndex = hideShowMoreButtonFingerprint.patternMatch!!.endIndex + val viewRegister = getInstruction(moveRegisterIndex).registerA + + val insertIndex = moveRegisterIndex + 1 + addInstruction( + insertIndex, + "invoke-static { v$viewRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" + + "->hideShowMoreButton(Landroid/view/View;)V", + ) + } + + // endregion + + // region crowdfunding box + crowdfundingBoxFingerprint.let { + it.method.apply { + val insertIndex = it.patternMatch!!.endIndex + val objectRegister = getInstruction(insertIndex).registerA - val insertIndex = moveRegisterIndex + 1 addInstruction( insertIndex, - "invoke-static { v$viewRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" + - "->hideShowMoreButton(Landroid/view/View;)V", + "invoke-static {v$objectRegister}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" + + "->hideCrowdfundingBox(Landroid/view/View;)V", ) } + } - // endregion + // endregion - // region crowdfunding box - crowdfundingBoxFingerprint.let { - it.method.apply { - val insertIndex = it.patternMatch!!.endIndex - val objectRegister = getInstruction(insertIndex).registerA + // region hide album cards - addInstruction( - insertIndex, - "invoke-static {v$objectRegister}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" + - "->hideCrowdfundingBox(Landroid/view/View;)V", - ) - } - } + albumCardsFingerprint.let { + it.method.apply { + val checkCastAnchorIndex = it.patternMatch!!.endIndex + val insertIndex = checkCastAnchorIndex + 1 + val register = getInstruction(checkCastAnchorIndex).registerA - // endregion - - // region hide album cards - - albumCardsFingerprint.let { - it.method.apply { - val checkCastAnchorIndex = it.patternMatch!!.endIndex - val insertIndex = checkCastAnchorIndex + 1 - val register = getInstruction(checkCastAnchorIndex).registerA - - addInstruction( - insertIndex, - "invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" + + addInstruction( + insertIndex, + "invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" + "->hideAlbumCard(Landroid/view/View;)V", - ) - } + ) } + } - // endregion + // endregion - // region hide floating microphone + // region hide floating microphone - showFloatingMicrophoneButtonFingerprint.method.apply { - val literalIndex = indexOfFirstLiteralInstructionOrThrow(fabButtonId) - val booleanIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.IGET_BOOLEAN) - val register = getInstruction(booleanIndex).registerA + showFloatingMicrophoneButtonFingerprint.method.apply { + val literalIndex = indexOfFirstLiteralInstructionOrThrow(fabButtonId) + val booleanIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.IGET_BOOLEAN) + val register = getInstruction(booleanIndex).registerA - addInstructions( - booleanIndex + 1, - """ + addInstructions( + booleanIndex + 1, + """ invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideFloatingMicrophoneButton(Z)Z move-result v$register """ + ) + } + + // endregion + + // region 'Yoodles' + + yoodlesImageViewFingerprint.method.apply { + findInstructionIndicesReversedOrThrow { + getReference()?.name == "setImageDrawable" + }.forEach { insertIndex -> + val drawableRegister = getInstruction(insertIndex).registerD + val imageViewRegister = getInstruction(insertIndex).registerC + + replaceInstruction( + insertIndex, + "invoke-static { v$imageViewRegister, v$drawableRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->" + + "setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V" ) } + } - // endregion + // endregion - // region 'Yoodles' - yoodlesImageViewFingerprint.method.apply { - findInstructionIndicesReversedOrThrow { - getReference()?.name == "setImageDrawable" - }.forEach { insertIndex -> - val drawableRegister = getInstruction(insertIndex).registerD - val imageViewRegister = getInstruction(insertIndex).registerC + // region hide view count - replaceInstruction( - insertIndex, - "invoke-static { v$imageViewRegister, v$drawableRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->" + - "setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V" - ) - } + hideViewCountFingerprint.method.apply { + val startIndex = hideViewCountFingerprint.patternMatch!!.startIndex + var returnStringRegister = getInstruction(startIndex).registerA + + // Find the instruction where the text dimension is retrieved. + val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow { + val reference = getReference() + opcode == Opcode.INVOKE_STATIC && + reference?.definingClass == "Landroid/util/TypedValue;" && + reference.returnType == "F" && + reference.name == "applyDimension" && + reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;") } - // endregion + // A float value is passed which is used to determine subtitle text size. + val floatDimensionRegister = getInstruction( + applyDimensionIndex + 1 + ).registerA - - // region hide view count - - hideViewCountFingerprint.method.apply { - val startIndex = hideViewCountFingerprint.patternMatch!!.startIndex - var returnStringRegister = getInstruction(startIndex).registerA - - // Find the instruction where the text dimension is retrieved. - val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow { - val reference = getReference() - opcode == Opcode.INVOKE_STATIC && - reference?.definingClass == "Landroid/util/TypedValue;" && - reference.returnType == "F" && - reference.name == "applyDimension" && - reference.parameterTypes == listOf("I", "F", "Landroid/util/DisplayMetrics;") - } - - // A float value is passed which is used to determine subtitle text size. - val floatDimensionRegister = getInstruction( - applyDimensionIndex + 1 - ).registerA - - addInstructions( - applyDimensionIndex - 1, - """ + addInstructions( + applyDimensionIndex - 1, + """ invoke-static { v$returnStringRegister, v$floatDimensionRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->modifyFeedSubtitleSpan(Landroid/text/SpannableString;F)Landroid/text/SpannableString; move-result-object v$returnStringRegister """ - ) - } + ) + } - // endregion + // endregion - // region hide filter bar + // region hide filter bar - /** - * Patch a [Method] with a given [instructions]. - * - * @param RegisterInstruction The type of instruction to get the register from. - * @param insertIndexOffset The offset to add to the end index of the [Match.patternMatch]. - * @param hookRegisterOffset The offset to add to the register of the hook. - * @param instructions The instructions to add with the register as a parameter. - */ - fun Fingerprint.patch( - insertIndexOffset: Int = 0, - hookRegisterOffset: Int = 0, - instructions: (Int) -> String, - ) = method.apply { - val endIndex = patternMatch!!.endIndex + /** + * Patch a [Method] with a given [instructions]. + * + * @param RegisterInstruction The type of instruction to get the register from. + * @param insertIndexOffset The offset to add to the end index of the [Match.patternMatch]. + * @param hookRegisterOffset The offset to add to the register of the hook. + * @param instructions The instructions to add with the register as a parameter. + */ + fun Fingerprint.patch( + insertIndexOffset: Int = 0, + hookRegisterOffset: Int = 0, + instructions: (Int) -> String, + ) = method.apply { + val endIndex = patternMatch!!.endIndex - val insertIndex = endIndex + insertIndexOffset - val register = - getInstruction(endIndex + hookRegisterOffset).registerA + val insertIndex = endIndex + insertIndexOffset + val register = + getInstruction(endIndex + hookRegisterOffset).registerA - addInstructions(insertIndex, instructions(register)) - } + addInstructions(insertIndex, instructions(register)) + } - filterBarHeightFingerprint.patch { register -> - """ + filterBarHeightFingerprint.patch { register -> + """ invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInFeed(I)I move-result v$register """ - } + } - searchResultsChipBarFingerprint.patch(-1, -2) { register -> - """ + searchResultsChipBarFingerprint.patch(-1, -2) { register -> + """ invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInSearch(I)I move-result v$register """ - } + } - relatedChipCloudFingerprint.patch(1) { register -> - "invoke-static { v$register }, " + + relatedChipCloudFingerprint.patch(1) { register -> + "invoke-static { v$register }, " + "$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V" - } } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt index 09c648738..6a9779a49 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/infocards/HideInfoCardsPatch.kt @@ -13,7 +13,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt index 4ee4e9259..5f4857053 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt @@ -5,7 +5,7 @@ import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt index 1109e910e..2d992cf39 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/hide/shorts/HideShortsComponentsPatch.kt @@ -14,7 +14,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch import app.revanced.patches.youtube.misc.playservice.is_19_41_or_greater diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt index a73570920..50db7c281 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/layout/returnyoutubedislike/ReturnYouTubeDislikePatch.kt @@ -11,7 +11,7 @@ import app.revanced.patches.shared.misc.settings.preference.PreferenceCategory import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch import app.revanced.patches.youtube.misc.playservice.is_19_33_or_greater diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt index a8f55f46c..aa9632f25 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/debugging/EnableDebuggingPatch.kt @@ -1,33 +1,22 @@ package app.revanced.patches.youtube.misc.debugging -import app.revanced.patches.all.misc.resources.addResources +import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch; import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch -import app.revanced.patches.shared.misc.settings.preference.SwitchPreference -import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.settingsPatch @Suppress("unused") val enableDebuggingPatch = enableDebuggingPatch( - block = { - dependsOn( - sharedExtensionPatch, - settingsPatch, + sharedExtensionPatch = sharedExtensionPatch, + settingsPatch = settingsPatch, + compatibleWithPackages = arrayOf( + "com.google.android.youtube" to setOf( + "19.34.42", + "20.07.39", + "20.13.41", + "20.14.43", ) - - compatibleWith( - "com.google.android.youtube"( - "19.34.42", - "20.07.39", - "20.13.41", - "20.14.43", - ) - ) - }, - executeBlock = { - addResources("youtube", "misc.debugging.enableDebuggingPatch") - }, + ), hookStringFeatureFlag = true, preferenceScreen = PreferenceScreen.MISC, - additionalDebugPreferences = listOf(SwitchPreference("revanced_debug_protobuffer")) ) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/Fingerprints.kt index 497bd3c89..9c97c5881 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/Fingerprints.kt @@ -1,58 +1,8 @@ package app.revanced.patches.youtube.misc.litho.filter import app.revanced.patcher.fingerprint -import app.revanced.util.containsLiteralInstruction import app.revanced.util.literal import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode - -internal val componentContextParserFingerprint = fingerprint { - strings("Number of bits must be positive") -} - -internal val componentCreateFingerprint = fingerprint { - strings( - "Element missing correct type extension", - "Element missing type" - ) -} - -internal val lithoFilterFingerprint = fingerprint { - accessFlags(AccessFlags.STATIC, AccessFlags.CONSTRUCTOR) - custom { _, classDef -> - classDef.endsWith("/LithoFilterPatch;") - } -} - -internal val protobufBufferReferenceFingerprint = fingerprint { - accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) - returns("V") - parameters("I", "Ljava/nio/ByteBuffer;") - opcodes( - Opcode.IPUT, - Opcode.INVOKE_VIRTUAL, - Opcode.MOVE_RESULT, - Opcode.SUB_INT_2ADDR, - ) -} - -internal val emptyComponentFingerprint = fingerprint { - accessFlags(AccessFlags.PRIVATE, AccessFlags.CONSTRUCTOR) - parameters() - strings("EmptyComponent") - custom { _, classDef -> - classDef.methods.filter { AccessFlags.STATIC.isSet(it.accessFlags) }.size == 1 - } -} - -internal val lithoThreadExecutorFingerprint = fingerprint { - accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) - parameters("I", "I", "I") - custom { method, classDef -> - classDef.superclass == "Ljava/util/concurrent/ThreadPoolExecutor;" && - method.containsLiteralInstruction(1L) // 1L = default thread timeout. - } -} internal val lithoComponentNameUpbFeatureFlagFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt index 81ae57cbb..f2a7dce18 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/misc/litho/filter/LithoFilterPatch.kt @@ -3,225 +3,68 @@ package app.revanced.patches.youtube.misc.litho.filter 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.InstructionExtensions.removeInstructions -import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction -import app.revanced.patcher.patch.bytecodePatch -import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch +import app.revanced.patcher.patch.BytecodePatchContext +import app.revanced.patches.shared.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.playservice.is_19_17_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater import app.revanced.patches.youtube.misc.playservice.is_20_05_or_greater import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.shared.conversionContextFingerprintToString -import app.revanced.util.addInstructionsAtControlFlowLabel -import app.revanced.util.findFreeRegister -import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstructionOrThrow -import app.revanced.util.indexOfFirstInstructionReversedOrThrow -import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode 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 +@Deprecated("Use the shared one instead", ReplaceWith("app.revanced.patches.shared.misc.litho.filter.addLithoFilter")) lateinit var addLithoFilter: (String) -> Unit private set -private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/components/LithoFilterPatch;" - -val lithoFilterPatch = bytecodePatch( - description = "Hooks the method which parses the bytes into a ComponentContext to filter components.", +val lithoFilterPatch = lithoFilterPatch( + componentCreateInsertionIndex = { + if (is_19_17_or_greater) { + indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT) + } else { + // 19.16 clobbers p2 so must check at start of the method + 0 + } + }, + conversionContextFingerprintToString = conversionContextFingerprintToString, + executeBlock = BytecodePatchContext::executeBlock, ) { - dependsOn( - sharedExtensionPatch, - versionCheckPatch, - ) - - var filterCount = 0 - - /** - * The following patch inserts a hook into the method that parses the bytes into a ComponentContext. - * This method contains a StringBuilder object that represents the pathBuilder of the component. - * The pathBuilder is used to filter components by their path. - * - * Additionally, the method contains a reference to the component's identifier. - * The identifier is used to filter components by their identifier. - * - * The protobuf buffer is passed along from a different injection point before the filtering occurs. - * The buffer is a large byte array that represents the component tree. - * This byte array is searched for strings that indicate the current component. - * - * All modifications done here must allow all the original code to still execute - * even when filtering, otherwise memory leaks or poor app performance may occur. - * - * The following pseudocode shows how this patch works: - * - * class SomeOtherClass { - * // Called before ComponentContextParser.parseComponent() method. - * public void someOtherMethod(ByteBuffer byteBuffer) { - * ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch. - * ... - * } - * } - * - * class CreateComponentClass { - * public Component createComponent() { - * ... - * - * if (extensionClass.shouldFilter(identifier, path)) { // Inserted by this patch. - * return emptyComponent; - * } - * return originalUnpatchedComponent; // Original code. - * } - * } - */ - execute { - // Remove dummy filter from extenion static field - // and add the filters included during patching. - lithoFilterFingerprint.method.apply { - removeInstructions(2, 4) // Remove dummy filter. - - addLithoFilter = { classDescriptor -> - addInstructions( - 2, - """ - new-instance v1, $classDescriptor - invoke-direct { v1 }, $classDescriptor->()V - const/16 v2, ${filterCount++} - aput-object v1, v0, v2 - """, - ) - } - } - - // region Pass the buffer into extension. - - protobufBufferReferenceFingerprint.method.addInstruction( - 0, - "invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->setProtoBuffer(Ljava/nio/ByteBuffer;)V", - ) - - // endregion - - // region Hook the method that parses bytes into a ComponentContext. - - // Allow the method to run to completion, and override the - // return value with an empty component if it should be filtered. - // It is important to allow the original code to always run to completion, - // otherwise high memory usage and poor app performance can occur. - - // Find the identifier/path fields of the conversion context. - val conversionContextIdentifierField = componentContextParserFingerprint.let { - // Identifier field is loaded just before the string declaration. - val index = it.method.indexOfFirstInstructionReversedOrThrow( - it.stringMatches!!.first().index - ) { - val reference = getReference() - reference?.definingClass == conversionContextFingerprintToString.originalClassDef.type - && reference.type == "Ljava/lang/String;" - } - - it.method.getInstruction(index).getReference()!! - } - - val conversionContextPathBuilderField = conversionContextFingerprintToString.originalClassDef - .fields.single { field -> field.type == "Ljava/lang/StringBuilder;" } - - // Find class and methods to create an empty component. - val builderMethodDescriptor = emptyComponentFingerprint.classDef.methods.single { - // The only static method in the class. - method -> AccessFlags.STATIC.isSet(method.accessFlags) - } - val emptyComponentField = classBy { - // Only one field that matches. - it.type == builderMethodDescriptor.returnType - }!!.immutableClass.fields.single() - - componentCreateFingerprint.method.apply { - val insertIndex = if (is_19_17_or_greater) { - indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT) - } else { - // 19.16 clobbers p2 so must check at start of the method and not at the return index. - 0 - } - - val freeRegister = findFreeRegister(insertIndex) - val identifierRegister = findFreeRegister(insertIndex, freeRegister) - val pathRegister = findFreeRegister(insertIndex, freeRegister, identifierRegister) - - addInstructionsAtControlFlowLabel( - insertIndex, - """ - move-object/from16 v$freeRegister, p2 - iget-object v$identifierRegister, v$freeRegister, $conversionContextIdentifierField - iget-object v$pathRegister, v$freeRegister, $conversionContextPathBuilderField - invoke-static { v$identifierRegister, v$pathRegister }, $EXTENSION_CLASS_DESCRIPTOR->isFiltered(Ljava/lang/String;Ljava/lang/StringBuilder;)Z - move-result v$freeRegister - if-eqz v$freeRegister, :unfiltered - - # Return an empty component - move-object/from16 v$freeRegister, p1 - invoke-static { v$freeRegister }, $builderMethodDescriptor - move-result-object v$freeRegister - iget-object v$freeRegister, v$freeRegister, $emptyComponentField - return-object v$freeRegister - - :unfiltered - nop - """ - ) - } - - // endregion - - - // region Change Litho thread executor to 1 thread to fix layout issue in unpatched YouTube. - - lithoThreadExecutorFingerprint.method.addInstructions( - 0, - """ - invoke-static { p1 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorCorePoolSize(I)I - move-result p1 - invoke-static { p2 }, $EXTENSION_CLASS_DESCRIPTOR->getExecutorMaxThreads(I)I - move-result p2 - """ - ) - - // endregion - - - // region A/B test of new Litho native code. - - // Turn off native code that handles litho component names. If this feature is on then nearly - // all litho components have a null name and identifier/path filtering is completely broken. - // - // Flag was removed in 20.05. It appears a new flag might be used instead (45660109L), - // but if the flag is forced on then litho filtering still works correctly. - if (is_19_25_or_greater && !is_20_05_or_greater) { - lithoComponentNameUpbFeatureFlagFingerprint.method.apply { - // Don't use return early, so the debug patch logs if this was originally on. - val insertIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN) - val register = getInstruction(insertIndex).registerA - - addInstruction(insertIndex, "const/4 v$register, 0x0") - } - } - - // Turn off a feature flag that enables native code of protobuf parsing (Upb protobuf). - // If this is enabled, then the litho protobuffer hook will always show an empty buffer - // since it's no longer handled by the hooked Java code. - lithoConverterBufferUpbFeatureFlagFingerprint.method.apply { - val index = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT) - val register = getInstruction(index).registerA - - addInstruction(index + 1, "const/4 v$register, 0x0") - } - - // endregion - } - - finalize { - lithoFilterFingerprint.method.replaceInstruction(0, "const/16 v0, $filterCount") - } + dependsOn(versionCheckPatch) +} + +private fun BytecodePatchContext.executeBlock() { + // region A/B test of new Litho native code. + + // Turn off native code that handles litho component names. If this feature is on then nearly + // all litho components have a null name and identifier/path filtering is completely broken. + // + // Flag was removed in 20.05. It appears a new flag might be used instead (45660109L), + // but if the flag is forced on then litho filtering still works correctly. + if (is_19_25_or_greater && !is_20_05_or_greater) { + lithoComponentNameUpbFeatureFlagFingerprint.method.apply { + // Don't use return early, so the debug patch logs if this was originally on. + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN) + val register = getInstruction(insertIndex).registerA + + addInstruction(insertIndex, "const/4 v$register, 0x0") + } + } + + // Turn off a feature flag that enables native code of protobuf parsing (Upb protobuf). + // If this is enabled, then the litho protobuffer hook will always show an empty buffer + // since it's no longer handled by the hooked Java code. + lithoConverterBufferUpbFeatureFlagFingerprint.method.apply { + val index = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT) + val register = getInstruction(index).registerA + + addInstruction(index + 1, "const/4 v$register, 0x0") + } + + // endregion + + // Set the addLithoFilter function to the one from the shared patch. + // This is done for backwards compatibility. + addLithoFilter = app.revanced.patches.shared.misc.litho.filter.addLithoFilter } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/AdvancedVideoQualityMenuPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/AdvancedVideoQualityMenuPatch.kt index 3e8aa23a1..6397a1184 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/AdvancedVideoQualityMenuPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/AdvancedVideoQualityMenuPatch.kt @@ -13,7 +13,7 @@ import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt index b46375571..9f905d6fc 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/speed/custom/CustomPlaybackSpeedPatch.kt @@ -18,7 +18,7 @@ import app.revanced.patches.shared.misc.settings.preference.InputType import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.TextPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.misc.litho.filter.addLithoFilter +import app.revanced.patches.shared.misc.litho.filter.addLithoFilter import app.revanced.patches.youtube.misc.litho.filter.lithoFilterPatch import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater import app.revanced.patches.youtube.misc.playservice.versionCheckPatch diff --git a/patches/src/main/resources/addresources/values/strings.xml b/patches/src/main/resources/addresources/values/strings.xml index 9c2a55e0e..e41691719 100644 --- a/patches/src/main/resources/addresources/values/strings.xml +++ b/patches/src/main/resources/addresources/values/strings.xml @@ -173,6 +173,14 @@ You will not be notified of any unexpected events." Flags saved Flags reset Flags copied to clipboard + Log protocol buffer + Debug logs include proto buffer + Debug logs do not include proto buffer + "Enabling this setting will log additional layout data, including on-screen text for some UI components. + +This can help identify components when creating custom filters. + +However, enabling this will also log some user data such as your IP address." Sanitize sharing links @@ -182,6 +190,17 @@ You will not be notified of any unexpected events." Shared links use youtube.com Shared links use music.youtube.com + + Custom filter + Hide components using custom filters + Enable custom filter + Custom filter is enabled + Custom filter is disabled + Custom filter + + List of component path builder strings to filter separated by new line + Invalid custom filter: %s + @@ -206,16 +225,6 @@ You will not be notified of any unexpected events." Shorts background play is disabled Shorts background play is enabled - - Log protocol buffer - Debug logs include proto buffer - Debug logs do not include proto buffer - "Enabling this setting will log additional layout data, including on-screen text for some UI components. - -This can help identify components when creating custom filters. - -However, enabling this will also log some user data such as your IP address." - Hide album cards Album cards are hidden @@ -446,15 +455,6 @@ If a Doodle is currently showing in your region and this hide setting is on, the Hide Thanks button Thanks button is hidden Thanks button is shown - Custom filter - Hide components using custom filters - Enable custom filter - Custom filter is enabled - Custom filter is disabled - Custom filter - - List of component path builder strings to filter separated by new line - Invalid custom filter: %s Hide view count View count is hidden in feed and search results View count is shown in feed and search results