Compare commits

..

13 Commits

Author SHA1 Message Date
semantic-release-bot
97e74157fa chore: Release v5.50.0-dev.1 [skip ci]
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.50.0-dev.1) (2026-01-22)

### Features

* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([89645dc](89645dcc2e))
2026-01-22 19:10:39 +01:00
Johannes Obermeier
5edd9dccae feat(YouTube Music): Add Unlock Android Auto Media Browser patch (#6477)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
(cherry picked from commit 89645dcc2e13603b8f2fedb5e16231cb396e5965)
2026-01-22 19:04:09 +01:00
semantic-release-bot
9c18e1e649 chore: Release v5.49.0-dev.1 [skip ci]
# [5.49.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.1-dev.1...v5.49.0-dev.1) (2026-01-22)

### Features

* **YouTube Music:** Add `Hide layout components` patch ([#6365](https://github.com/ReVanced/revanced-patches/issues/6365)) ([71ce823](71ce8230a9))
2026-01-22 17:43:08 +00:00
rospino74
71ce8230a9 feat(YouTube Music): Add Hide layout components patch (#6365)
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-22 18:36:37 +01:00
github-actions[bot]
156441d3cf chore: Sync translations (#6516)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2026-01-22 18:21:28 +01:00
oSumAtrIX
634f47ef84 ci: Set necessary env vars for running Gradle task 2026-01-22 17:37:44 +01:00
oSumAtrIX
eeb133325e ci: Properly implement Crowdin strings processing 2026-01-22 17:37:44 +01:00
semantic-release-bot
e8d58ca9af chore: Release v5.48.1-dev.1 [skip ci]
## [5.48.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.48.1-dev.1) (2026-01-21)

### Bug Fixes

* Disable `Prevent screenshot detection` by default ([#6511](https://github.com/ReVanced/revanced-patches/issues/6511)) ([5b5c502](5b5c50254d))
2026-01-21 17:49:31 +00:00
Sayanth
5b5c50254d fix: Disable Prevent screenshot detection by default (#6511) 2026-01-21 18:44:04 +01:00
semantic-release-bot
ef052c0d8f chore: Release v5.48.0 [skip ci]
# [5.48.0](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0) (2026-01-19)

### Bug Fixes

* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](eecc44b956))
* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](44e7dbcf4d))
* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](789f0a5628))
* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](71c6cb569e))
* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](a429824bb7))
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](87247590de))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](dc69f2433e))

### Features

* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](4cc315952d))
* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](83c0127ebb))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](6312fe8d60))
* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](6bb6281149))
* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](8725a49ba3))
* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](18c0b04f0c))
* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](3401467a6d))
* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](d25dcfe49a))
* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](e0f33468e6))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](315931cbf8))
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](4c4ba1c78c))
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](778d13ce8b))
* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](c47beae213))
* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](f5cbb31724))
* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](8f3f4c95bb))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](b42ae27ce6))
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](19f146c01d))
2026-01-19 09:28:58 +00:00
oSumAtrIX
d9fa580222 chore: Merge branch dev to main (#6385) 2026-01-19 10:25:41 +01:00
semantic-release-bot
182224c79d chore: Release v5.48.0-dev.13 [skip ci]
# [5.48.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.12...v5.48.0-dev.13) (2026-01-19)

### Features

* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](83c0127ebb))
2026-01-19 09:21:21 +00:00
xehpuk
83c0127ebb feat: Add Prevent screenshot detection patch (#6482)
Co-authored-by: Swakshan <56347042+Swakshan@users.noreply.github.com>
Co-authored-by: oSumAtrIX <johan.melkonyan1@web.de>
2026-01-19 10:16:30 +01:00
129 changed files with 59969 additions and 59738 deletions

View File

@@ -16,10 +16,11 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Preprocess strings - name: Process strings
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ORG_GRADLE_PROJECT_githubPackagesUsername: ${{ github.actor }}
run: ./gradlew clean preprocessCrowdinStrings ORG_GRADLE_PROJECT_githubPackagesPassword: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew processStringsForCrowdin
- name: Push strings - name: Push strings
uses: crowdin/github-action@v2 uses: crowdin/github-action@v2

View File

@@ -1,3 +1,65 @@
# [5.50.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.50.0-dev.1) (2026-01-22)
### Features
* **YouTube Music:** Add `Unlock Android Auto Media Browser` patch ([#6477](https://github.com/ReVanced/revanced-patches/issues/6477)) ([89645dc](https://github.com/ReVanced/revanced-patches/commit/89645dcc2e13603b8f2fedb5e16231cb396e5965))
# [5.49.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.1-dev.1...v5.49.0-dev.1) (2026-01-22)
### Features
* **YouTube Music:** Add `Hide layout components` patch ([#6365](https://github.com/ReVanced/revanced-patches/issues/6365)) ([71ce823](https://github.com/ReVanced/revanced-patches/commit/71ce8230a959dcaf2d8cd5dad1a4f21b88819aa0))
## [5.48.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v5.48.0...v5.48.1-dev.1) (2026-01-21)
### Bug Fixes
* Disable `Prevent screenshot detection` by default ([#6511](https://github.com/ReVanced/revanced-patches/issues/6511)) ([5b5c502](https://github.com/ReVanced/revanced-patches/commit/5b5c50254d533faa0e04d542f4859cbef610713e))
# [5.48.0](https://github.com/ReVanced/revanced-patches/compare/v5.47.0...v5.48.0) (2026-01-19)
### Bug Fixes
* **Boost for Reddit - Fix missing audio in video downloads:** Make it work again by reflecting Reddits latest changes ([#6500](https://github.com/ReVanced/revanced-patches/issues/6500)) ([eecc44b](https://github.com/ReVanced/revanced-patches/commit/eecc44b9567bf2ca72ac99e0dafa483a6803c0f9))
* **Disney+ - Skip ads:** Remove unsupported package names ([#6422](https://github.com/ReVanced/revanced-patches/issues/6422)) ([44e7dbc](https://github.com/ReVanced/revanced-patches/commit/44e7dbcf4d7eaf94dd0164baba847d3e19250154))
* Fix build error introduced in `4046bee` ([#6417](https://github.com/ReVanced/revanced-patches/issues/6417)) ([789f0a5](https://github.com/ReVanced/revanced-patches/commit/789f0a562861825065633d172445ebf35a1ba8d8))
* Fix compilation error introduced in `6bb6281` ([#6409](https://github.com/ReVanced/revanced-patches/issues/6409)) ([71c6cb5](https://github.com/ReVanced/revanced-patches/commit/71c6cb569ebf7b93cf73ee391839e5220557ce7c))
* Fix compilation error introduced in dc69f243 ([#6392](https://github.com/ReVanced/revanced-patches/issues/6392)) ([a429824](https://github.com/ReVanced/revanced-patches/commit/a429824bb77b49aea14b0b54f2204ae24d5209a1))
* **Instagram:** `Sanitize sharing links` ([#6483](https://github.com/ReVanced/revanced-patches/issues/6483)) ([8724759](https://github.com/ReVanced/revanced-patches/commit/87247590de3db74680cb02ba1d87bf683b2269e2))
* **YouTube - Hide layout components:** Hide new type of crowdfunding box ([#6380](https://github.com/ReVanced/revanced-patches/issues/6380)) ([dc69f24](https://github.com/ReVanced/revanced-patches/commit/dc69f2433e2650654e2dffdd76b0b0c8a52bf515))
### Features
* Add `Disable Sentry telemetry` patch ([#6416](https://github.com/ReVanced/revanced-patches/issues/6416)) ([4cc3159](https://github.com/ReVanced/revanced-patches/commit/4cc315952db557c565872de9e8484805f2e42305))
* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](https://github.com/ReVanced/revanced-patches/commit/83c0127ebb8f53ab8a067758619faaac5596c145))
* Disable Play Integrity patch ([#6412](https://github.com/ReVanced/revanced-patches/issues/6412)) ([6312fe8](https://github.com/ReVanced/revanced-patches/commit/6312fe8d60da24465c0c1b0fa4e94ceb79873d9c))
* **Instagram - Hides navigation buttons:** Add more buttons to hide ([#6390](https://github.com/ReVanced/revanced-patches/issues/6390)) ([6bb6281](https://github.com/ReVanced/revanced-patches/commit/6bb62811493da04812cc3e392e68d874f95cbef9))
* **Instagram:** Add `Hide highlights tray` patch ([#6489](https://github.com/ReVanced/revanced-patches/issues/6489)) ([8725a49](https://github.com/ReVanced/revanced-patches/commit/8725a49ba3a06fee0280ffcf4be62cd960cd301e))
* **Instagram:** Add `Remove build expired popup` patch ([#6488](https://github.com/ReVanced/revanced-patches/issues/6488)) ([18c0b04](https://github.com/ReVanced/revanced-patches/commit/18c0b04f0cd1bf8cd78b05af3b8ebe3a6a5f9e48))
* **Instagram:** Disable `Disable Reels scrolling` by default ([3401467](https://github.com/ReVanced/revanced-patches/commit/3401467a6d49fc75b6757a15e5c848330c1b7307))
* **Letterboxd:** Add `Unlock app icons` patch ([#6415](https://github.com/ReVanced/revanced-patches/issues/6415)) ([d25dcfe](https://github.com/ReVanced/revanced-patches/commit/d25dcfe49ac331c9b3dca739ba0be95dbab669cc))
* **ProtonVPN:** Add `Unlock split tunneling` patch ([#6353](https://github.com/ReVanced/revanced-patches/issues/6353)) ([e0f3346](https://github.com/ReVanced/revanced-patches/commit/e0f33468e6e96b9f10cf35ec67622d6488528c90))
* **SBS On Demand:** Add `Remove ads` patch ([#6378](https://github.com/ReVanced/revanced-patches/issues/6378)) ([315931c](https://github.com/ReVanced/revanced-patches/commit/315931cbf8f61cd4b3a54ace1ff03685d748614c))
* **Strava:** Add `Add 'Give Kudos' button to 'Group Activity'` patch ([#6475](https://github.com/ReVanced/revanced-patches/issues/6475)) ([4c4ba1c](https://github.com/ReVanced/revanced-patches/commit/4c4ba1c78c9f4568a2b572f5c69e9c6c734e1a7f))
* **Strava:** Add `Add media download` patch ([#6449](https://github.com/ReVanced/revanced-patches/issues/6449)) ([778d13c](https://github.com/ReVanced/revanced-patches/commit/778d13ce8b28ca6df3a665530320e4a21a27ae44))
* **Strava:** Add `Block Snowplow tracking` patch ([#6413](https://github.com/ReVanced/revanced-patches/issues/6413)) ([c47beae](https://github.com/ReVanced/revanced-patches/commit/c47beae21376dd17ab8bc09afe73e9094481bde9))
* **Strava:** Add `Disable Quick Edit` patch ([#6452](https://github.com/ReVanced/revanced-patches/issues/6452)) ([f5cbb31](https://github.com/ReVanced/revanced-patches/commit/f5cbb31724d15f7e939b96ee0186fd0a108f9fdc))
* **Strava:** Add `Enable password login` patch ([#6396](https://github.com/ReVanced/revanced-patches/issues/6396)) ([8f3f4c9](https://github.com/ReVanced/revanced-patches/commit/8f3f4c95bb8f151fc9a2c272bf7d0e905c2f01fc))
* **Strava:** Add `Overwrite media upload parameters` patch ([#6410](https://github.com/ReVanced/revanced-patches/issues/6410)) ([b42ae27](https://github.com/ReVanced/revanced-patches/commit/b42ae27ce66ebad9e9cfc5b70fc121df5bad7567))
* **YouTube:** Add `Pause on audio interrupt` patch ([#6464](https://github.com/ReVanced/revanced-patches/issues/6464)) ([19f146c](https://github.com/ReVanced/revanced-patches/commit/19f146c01dc381b3cccd61e61ba4901872ff12d8))
# [5.48.0-dev.13](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.12...v5.48.0-dev.13) (2026-01-19)
### Features
* Add `Prevent screenshot detection` patch ([#6482](https://github.com/ReVanced/revanced-patches/issues/6482)) ([83c0127](https://github.com/ReVanced/revanced-patches/commit/83c0127ebb8f53ab8a067758619faaac5596c145))
# [5.48.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.11...v5.48.0-dev.12) (2026-01-19) # [5.48.0-dev.12](https://github.com/ReVanced/revanced-patches/compare/v5.48.0-dev.11...v5.48.0-dev.12) (2026-01-19)

View File

@@ -4,12 +4,12 @@ import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE; import static java.lang.Boolean.TRUE;
import static app.revanced.extension.shared.settings.Setting.parent; 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.BooleanSetting;
import app.revanced.extension.shared.settings.EnumSetting; import app.revanced.extension.shared.settings.EnumSetting;
import app.revanced.extension.shared.spoof.ClientType; import app.revanced.extension.shared.spoof.ClientType;
public class Settings extends BaseSettings { public class Settings extends YouTubeAndMusicSettings {
// Ads // Ads
public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true); public static final BooleanSetting HIDE_VIDEO_ADS = new BooleanSetting("revanced_music_hide_video_ads", TRUE, true);

View File

@@ -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; 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.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.ByteTrieSearch; 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. * Allows custom filtering using a path and optionally a proto buffer string.
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
final class CustomFilter extends Filter { public final class CustomFilter extends Filter {
private static void showInvalidSyntaxToast(@NonNull String expression) { private static void showInvalidSyntaxToast(@NonNull String expression) {
Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression)); Utils.showToastLong(str("revanced_custom_filter_toast_invalid_syntax", expression));
@@ -45,7 +47,7 @@ final class CustomFilter extends Filter {
@NonNull @NonNull
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
static Collection<CustomFilterGroup> parseCustomFilterGroups() { static Collection<CustomFilterGroup> parseCustomFilterGroups() {
String rawCustomFilterText = Settings.CUSTOM_FILTER_STRINGS.get(); String rawCustomFilterText = YouTubeAndMusicSettings.CUSTOM_FILTER_STRINGS.get();
if (rawCustomFilterText.isBlank()) { if (rawCustomFilterText.isBlank()) {
return Collections.emptyList(); return Collections.emptyList();
} }
@@ -100,7 +102,7 @@ final class CustomFilter extends Filter {
ByteTrieSearch bufferSearch; ByteTrieSearch bufferSearch;
CustomFilterGroup(boolean startsWith, @NonNull String path) { CustomFilterGroup(boolean startsWith, @NonNull String path) {
super(Settings.CUSTOM_FILTER, path); super(YouTubeAndMusicSettings.CUSTOM_FILTER, path);
this.startsWith = startsWith; this.startsWith = startsWith;
} }
@@ -145,7 +147,7 @@ final class CustomFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// All callbacks are custom filter groups. // All callbacks are custom filter groups.
CustomFilterGroup custom = (CustomFilterGroup) matchedGroup; CustomFilterGroup custom = (CustomFilterGroup) matchedGroup;
@@ -159,4 +161,4 @@ final class CustomFilter extends Filter {
return custom.bufferSearch.matches(buffer); return custom.bufferSearch.matches(buffer);
} }
} }

View File

@@ -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.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; 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. * Filters litho based components.
* *
@@ -14,11 +17,11 @@ import java.util.List;
* either an identifier or a path. * either an identifier or a path.
* Then inside {@link #isFiltered(String, String, byte[], StringFilterGroup, FilterContentType, int)} * 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) * 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. * All callbacks must be registered before the constructor completes.
*/ */
abstract class Filter { public abstract class Filter {
public enum FilterContentType { public enum FilterContentType {
IDENTIFIER, IDENTIFIER,
@@ -65,7 +68,7 @@ abstract class Filter {
* @param contentIndex Matched index of the identifier or path. * @param contentIndex Matched index of the identifier or path.
* @return True if the litho component should be filtered out. * @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) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
return true; return true;
} }

View File

@@ -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<T> {
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<String> {
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<byte[]> {
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);
}
}
}

View File

@@ -1,21 +1,22 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.shared.patches.litho;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import java.util.*; import java.util.*;
import java.util.function.Consumer;
import app.revanced.extension.shared.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.TrieSearch; 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<V, T extends FilterGroup<V>> implements Iterable<T> { public abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<T> {
private final List<T> filterGroups = new ArrayList<>(); private final List<T> filterGroups = new ArrayList<>();
private final TrieSearch<V> search = createSearchGraph(); private final TrieSearch<V> search = createSearchGraph();
@SafeVarargs @SafeVarargs
protected final void addAll(final T... groups) { public final void addAll(final T... groups) {
filterGroups.addAll(Arrays.asList(groups)); filterGroups.addAll(Arrays.asList(groups));
for (T group : groups) { for (T group : groups) {
@@ -41,18 +42,7 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
return filterGroups.iterator(); return filterGroups.iterator();
} }
@Override public FilterGroup.FilterGroupResult check(V stack) {
public void forEach(@NonNull Consumer<? super T> action) {
filterGroups.forEach(action);
}
@NonNull
@Override
public Spliterator<T> spliterator() {
return filterGroups.spliterator();
}
protected FilterGroup.FilterGroupResult check(V stack) {
FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult(); FilterGroup.FilterGroupResult result = new FilterGroup.FilterGroupResult();
search.matches(stack, result); search.matches(stack, result);
return result; return result;
@@ -60,21 +50,21 @@ abstract class FilterGroupList<V, T extends FilterGroup<V>> implements Iterable<
} }
protected abstract TrieSearch<V> createSearchGraph(); protected abstract TrieSearch<V> createSearchGraph();
}
final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> { public static final class StringFilterGroupList extends FilterGroupList<String, StringFilterGroup> {
protected StringTrieSearch createSearchGraph() { protected StringTrieSearch createSearchGraph() {
return new StringTrieSearch(); return new StringTrieSearch();
}
} }
}
/** /**
* If searching for a single byte pattern, then it is slightly better to use * 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 * {@link ByteArrayFilterGroup#check(byte[])} as it uses KMP which is faster
* than a prefix tree to search for only 1 pattern. * than a prefix tree to search for only 1 pattern.
*/ */
final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> { public static final class ByteArrayFilterGroupList extends FilterGroupList<byte[], ByteArrayFilterGroup> {
protected ByteTrieSearch createSearchGraph() { protected ByteTrieSearch createSearchGraph() {
return new ByteTrieSearch(); return new ByteTrieSearch();
}
} }
} }

View File

@@ -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.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@@ -7,9 +7,11 @@ import java.nio.ByteBuffer;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.StringTrieSearch; 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") @SuppressWarnings("unused")
public final class LithoFilterPatch { public final class LithoFilterPatch {
@@ -36,7 +38,7 @@ public final class LithoFilterPatch {
builder.append(identifier); builder.append(identifier);
builder.append(" Path: "); builder.append(" Path: ");
builder.append(path); builder.append(path);
if (Settings.DEBUG_PROTOBUFFER.get()) { if (YouTubeAndMusicSettings.DEBUG_PROTOBUFFER.get()) {
builder.append(" BufferStrings: "); builder.append(" BufferStrings: ");
findAsciiStrings(builder, buffer); findAsciiStrings(builder, buffer);
} }

View File

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

View File

@@ -11,6 +11,9 @@ import java.util.List;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.StringTrieSearch; 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; import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @SuppressWarnings("unused")
@@ -153,8 +156,8 @@ public final class AdsFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == playerShoppingShelf) { if (matchedGroup == playerShoppingShelf) {
return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered(); return contentIndex == 0 && playerShoppingShelfBuffer.check(buffer).isFiltered();
} }

View File

@@ -1,5 +1,7 @@
package app.revanced.extension.youtube.patches.components; 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.patches.playback.quality.AdvancedVideoQualityMenuPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@@ -19,7 +21,7 @@ public final class AdvancedVideoQualityMenuFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
isVideoQualityMenuVisible = true; isVideoQualityMenuVisible = true;

View File

@@ -1,9 +1,13 @@
package app.revanced.extension.youtube.patches.components; 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; import app.revanced.extension.youtube.settings.Settings;
@SuppressWarnings("unused") @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 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_PREFIX = "video_action_bar.e";
private static final String VIDEO_ACTION_BAR_PATH = "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 @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == likeSubscribeGlow) { if (matchedGroup == likeSubscribeGlow) {
return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX)) return (path.startsWith(VIDEO_ACTION_BAR_PATH_PREFIX) || path.startsWith(COMPACT_CHANNEL_BAR_PATH_PREFIX))

View File

@@ -1,10 +1,12 @@
package app.revanced.extension.youtube.patches.components; 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.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
final class CommentsFilter extends Filter { public final class CommentsFilter extends Filter {
private static final String COMMENT_COMPOSER_PATH = "comment_composer.e"; private static final String COMMENT_COMPOSER_PATH = "comment_composer.e";
@@ -88,8 +90,8 @@ final class CommentsFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == chipBar) { if (matchedGroup == chipBar) {
// Playlist sort button uses same components and must only filter if the player is opened. // Playlist sort button uses same components and must only filter if the player is opened.
return PlayerType.getCurrent().isMaximizedOrFullscreen() return PlayerType.getCurrent().isMaximizedOrFullscreen()

View File

@@ -1,11 +1,14 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.shared.StringTrieSearch; 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.settings.Settings;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
@SuppressWarnings("unused") @SuppressWarnings("unused")
final class DescriptionComponentsFilter extends Filter { public final class DescriptionComponentsFilter extends Filter {
private static final String INFOCARDS_SECTION_PATH = "infocards_section.e"; private static final String INFOCARDS_SECTION_PATH = "infocards_section.e";
@@ -128,8 +131,8 @@ final class DescriptionComponentsFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == aiGeneratedVideoSummarySection || matchedGroup == hypePoints) { if (matchedGroup == aiGeneratedVideoSummarySection || matchedGroup == hypePoints) {
// Only hide if player is open, in case this component is used somewhere else. // Only hide if player is open, in case this component is used somewhere else.

View File

@@ -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<T> {
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<String> {
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<byte[]> {
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);
}
}

View File

@@ -1,6 +1,8 @@
package app.revanced.extension.youtube.patches.components; package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.youtube.settings.Settings; 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") @SuppressWarnings("unused")
public final class HideInfoCardsFilter extends Filter { public final class HideInfoCardsFilter extends Filter {

View File

@@ -17,6 +17,8 @@ import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.ByteTrieSearch; import app.revanced.extension.shared.ByteTrieSearch;
import app.revanced.extension.shared.StringTrieSearch; import app.revanced.extension.shared.StringTrieSearch;
import app.revanced.extension.shared.TrieSearch; 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.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.NavigationBar;
import app.revanced.extension.youtube.shared.PlayerType; 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. * - When using whole word syntax, some keywords may need additional pluralized variations.
*/ */
@SuppressWarnings("unused") @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. * Strings found in the buffer for every videos. Full strings should be specified.
@@ -554,8 +556,8 @@ final class KeywordContentFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (contentIndex != 0 && matchedGroup == startsWithFilter) { if (contentIndex != 0 && matchedGroup == startsWithFilter) {
return false; return false;
} }

View File

@@ -14,6 +14,9 @@ import androidx.annotation.Nullable;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; import app.revanced.extension.shared.Utils;
import app.revanced.extension.shared.StringTrieSearch; 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.patches.ChangeHeaderPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.NavigationBar;
@@ -342,7 +345,7 @@ public final class LayoutComponentsFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
// This identifier is used not only in players but also in search results: // This identifier is used not only in players but also in search results:
// https://github.com/ReVanced/revanced-patches/issues/3245 // https://github.com/ReVanced/revanced-patches/issues/3245

View File

@@ -1,5 +1,7 @@
package app.revanced.extension.youtube.patches.components; 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.patches.playback.speed.CustomPlaybackSpeedPatch;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
@@ -36,7 +38,7 @@ public final class PlaybackSpeedMenuFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == oldPlaybackMenuGroup) { if (matchedGroup == oldPlaybackMenuGroup) {
isOldPlaybackSpeedMenuVisible = true; isOldPlaybackSpeedMenuVisible = true;

View File

@@ -3,13 +3,16 @@ package app.revanced.extension.youtube.patches.components;
import app.revanced.extension.shared.settings.BaseSettings; import app.revanced.extension.shared.settings.BaseSettings;
import app.revanced.extension.shared.settings.Setting; import app.revanced.extension.shared.settings.Setting;
import app.revanced.extension.shared.spoof.SpoofVideoStreamsPatch; 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.settings.Settings;
import app.revanced.extension.youtube.shared.ShortsPlayerState; import app.revanced.extension.youtube.shared.ShortsPlayerState;
import java.util.List; import java.util.List;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class PlayerFlyoutMenuItemsFilter extends Filter { public final class PlayerFlyoutMenuItemsFilter extends Filter {
public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability { public static final class HideAudioFlyoutMenuAvailability implements Setting.Availability {
@Override @Override
@@ -94,7 +97,7 @@ public class PlayerFlyoutMenuItemsFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (matchedGroup == videoQualityMenuFooter) { if (matchedGroup == videoQualityMenuFooter) {
return true; return true;

View File

@@ -13,6 +13,9 @@ import app.revanced.extension.youtube.patches.VideoInformation;
import app.revanced.extension.youtube.settings.Settings; import app.revanced.extension.youtube.settings.Settings;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.TrieSearch; 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. * Searches for video id's in the proto buffer of Shorts dislike.
@@ -84,13 +87,13 @@ public final class ReturnYouTubeDislikeFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) { if (!Settings.RYD_ENABLED.get() || !Settings.RYD_SHORTS.get()) {
return false; return false;
} }
FilterGroup.FilterGroupResult result = videoIdFilterGroup.check(buffer); FilterGroupResult result = videoIdFilterGroup.check(buffer);
if (result.isFiltered()) { if (result.isFiltered()) {
String matchedVideoId = findVideoId(buffer); String matchedVideoId = findVideoId(buffer);
// Matched video will be null if in incognito mode. // Matched video will be null if in incognito mode.

View File

@@ -11,6 +11,9 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import app.revanced.extension.shared.Logger; 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.settings.Settings;
import app.revanced.extension.youtube.shared.NavigationBar; import app.revanced.extension.youtube.shared.NavigationBar;
import app.revanced.extension.youtube.shared.PlayerType; import app.revanced.extension.youtube.shared.PlayerType;
@@ -339,7 +342,7 @@ public final class ShortsFilter extends Filter {
} }
@Override @Override
boolean isFiltered(String identifier, String path, byte[] buffer, public boolean isFiltered(String identifier, String path, byte[] buffer,
StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) {
if (contentType == FilterContentType.PATH) { if (contentType == FilterContentType.PATH) {
if (matchedGroup == subscribeButton || matchedGroup == joinButton if (matchedGroup == subscribeButton || matchedGroup == joinButton

View File

@@ -32,6 +32,7 @@ import android.graphics.Color;
import app.revanced.extension.shared.Logger; import app.revanced.extension.shared.Logger;
import app.revanced.extension.shared.Utils; 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.BaseSettings;
import app.revanced.extension.shared.settings.BooleanSetting; import app.revanced.extension.shared.settings.BooleanSetting;
import app.revanced.extension.shared.settings.EnumSetting; 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.sponsorblock.SponsorBlockSettings;
import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle; import app.revanced.extension.youtube.swipecontrols.SwipeControlsConfigurationProvider.SwipeOverlayStyle;
public class Settings extends BaseSettings { public class Settings extends YouTubeAndMusicSettings {
// Video // Video
public static final BooleanSetting ADVANCED_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_advanced_video_quality_menu", TRUE); 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); 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, public static final BooleanSetting CHANGE_START_PAGE_ALWAYS = new BooleanSetting("revanced_change_start_page_always", FALSE, true,
new ChangeStartPageTypeAvailability()); 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)); 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 // Navigation buttons
public static final BooleanSetting HIDE_HOME_BUTTON = new BooleanSetting("revanced_hide_home_button", FALSE, true); 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); 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<ClientType> 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 EnumSetting<ClientType> 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, 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()); "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 // Swipe controls
public static final BooleanSetting SWIPE_CHANGE_VIDEO = new BooleanSetting("revanced_swipe_change_video", FALSE, true); 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, public static final IntegerSetting SWIPE_MAGNITUDE_THRESHOLD = new IntegerSetting("revanced_swipe_threshold", 30, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); 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 IntegerSetting SWIPE_VOLUME_SENSITIVITY = new IntegerSetting("revanced_swipe_volume_sensitivity", 1, true, parent(SWIPE_VOLUME));
public static final EnumSetting<SwipeOverlayStyle> SWIPE_OVERLAY_STYLE = new EnumSetting<>("revanced_swipe_overlay_style", SwipeOverlayStyle.HORIZONTAL,true, public static final EnumSetting<SwipeOverlayStyle> SWIPE_OVERLAY_STYLE = new EnumSetting<>("revanced_swipe_overlay_style", SwipeOverlayStyle.HORIZONTAL, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 14, true, public static final IntegerSetting SWIPE_OVERLAY_TEXT_SIZE = new IntegerSetting("revanced_swipe_text_overlay_size", 14, true,
parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME)); parentsAny(SWIPE_BRIGHTNESS, SWIPE_VOLUME));
@@ -411,7 +405,9 @@ public class Settings extends BaseSettings {
// SponsorBlock // SponsorBlock
public static final BooleanSetting SB_ENABLED = new BooleanSetting("sb_enabled", TRUE); 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 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 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)); 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); public static final StringSetting SB_CATEGORY_UNSUBMITTED_COLOR = new StringSetting("sb_unsubmitted_color", "#FFFFFFFF", false, false);
// Deprecated migrations // 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_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); 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. // or is spoofing to a version the same or newer than this app.
if (!SPOOF_APP_VERSION_TARGET.isSetToDefault() && if (!SPOOF_APP_VERSION_TARGET.isSetToDefault() &&
(SPOOF_APP_VERSION_TARGET.get().compareTo(SPOOF_APP_VERSION_TARGET.defaultValue) < 0 (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"); Logger.printInfo(() -> "Resetting spoof app version");
SPOOF_APP_VERSION_TARGET.resetToDefault(); SPOOF_APP_VERSION_TARGET.resetToDefault();
SPOOF_APP_VERSION.resetToDefault(); SPOOF_APP_VERSION.resetToDefault();

View File

@@ -3,4 +3,4 @@ org.gradle.jvmargs = -Xms512M -Xmx2048M
org.gradle.parallel = true org.gradle.parallel = true
android.useAndroidX = true android.useAndroidX = true
kotlin.code.style = official kotlin.code.style = official
version = 5.48.0-dev.12 version = 5.50.0-dev.1

View File

@@ -124,6 +124,10 @@ public final class app/revanced/patches/all/misc/screencapture/RemoveScreenCaptu
public static final fun getRemoveScreenCaptureRestrictionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRemoveScreenCaptureRestrictionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/all/misc/screenshot/PreventScreenshotDetectionPatchKt {
public static final fun getPreventScreenshotDetectionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/all/misc/screenshot/RemoveScreenshotRestrictionPatchKt { public final class app/revanced/patches/all/misc/screenshot/RemoveScreenshotRestrictionPatchKt {
public static final fun getRemoveScreenshotRestrictionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getRemoveScreenshotRestrictionPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -468,6 +472,10 @@ public final class app/revanced/patches/music/layout/compactheader/HideCategoryB
public static final fun getHideCategoryBar ()Lapp/revanced/patcher/patch/BytecodePatch; 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 final class app/revanced/patches/music/layout/miniplayercolor/ChangeMiniplayerColorKt {
public static final fun getChangeMiniplayerColor ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getChangeMiniplayerColor ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -493,6 +501,10 @@ public final class app/revanced/patches/music/misc/androidauto/BypassCertificate
public static final fun getBypassCertificateChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getBypassCertificateChecksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
public final class app/revanced/patches/music/misc/androidauto/UnlockAndroidAutoMediaBrowserPatchKt {
public static final fun getUnlockAndroidAutoMediaBrowserPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
}
public final class app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatchKt { public final class app/revanced/patches/music/misc/backgroundplayback/BackgroundPlaybackPatchKt {
public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getBackgroundPlaybackPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -517,6 +529,10 @@ public final class app/revanced/patches/music/misc/gms/GmsCoreSupportPatchKt {
public static final fun getGmsCoreSupportPatch ()Lapp/revanced/patcher/patch/BytecodePatch; 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 final class app/revanced/patches/music/misc/privacy/SanitizeSharingLinksPatchKt {
public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch; public static final fun getSanitizeSharingLinksPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
} }
@@ -928,6 +944,12 @@ public final class app/revanced/patches/shared/misc/hex/Replacement {
public final fun getReplacementBytesPadded ()[B 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 class app/revanced/patches/shared/misc/mapping/ResourceElement {
public final fun component1 ()Ljava/lang/String; public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String;

View File

@@ -1,3 +1,10 @@
import org.w3c.dom.*
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.OutputKeys
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
group = "app.revanced" group = "app.revanced"
patches { patches {
@@ -22,25 +29,6 @@ dependencies {
compileOnly(project(":patches:stub")) compileOnly(project(":patches:stub"))
} }
tasks {
register<JavaExec>("preprocessCrowdinStrings") {
description = "Preprocess strings for Crowdin push"
dependsOn(compileKotlin)
classpath = sourceSets["main"].runtimeClasspath
mainClass.set("app.revanced.util.CrowdinPreprocessorKt")
args = listOf(
"src/main/resources/addresources/values/strings.xml",
// Ideally this would use build/tmp/crowdin/strings.xml
// But using that does not work with Crowdin pull because
// it does not recognize the strings.xml file belongs to this project.
"src/main/resources/addresources/values/strings.xml"
)
}
}
kotlin { kotlin {
compilerOptions { compilerOptions {
freeCompilerArgs = listOf("-Xcontext-receivers") freeCompilerArgs = listOf("-Xcontext-receivers")
@@ -55,4 +43,91 @@ publishing {
credentials(PasswordCredentials::class) credentials(PasswordCredentials::class)
} }
} }
} }
tasks.register("processStringsForCrowdin") {
description = "Process strings file for Crowdin by commenting out non-standard tags."
doLast {
// Comment out the non-standard tags. Otherwise, Crowdin interprets the file
// not as Android but instead a generic xml file where strings are
// identified by xml position and not key
val stringsXmlFile = project.projectDir.resolve("src/main/resources/addresources/values/strings.xml")
val builder = DocumentBuilderFactory.newInstance().apply {
isIgnoringComments = false
isCoalescing = false
isNamespaceAware = false
}.newDocumentBuilder()
val document = builder.newDocument()
val root = document.createElement("resources").also(document::appendChild)
fun walk(node: Node, appId: String? = null, patchId: String? = null, insideResources: Boolean = false) {
fun walkChildren(el: Element, appId: String?, patchId: String?, insideResources: Boolean) {
val children = el.childNodes
for (i in 0 until children.length) {
walk(children.item(i), appId, patchId, insideResources)
}
}
when (node.nodeType) {
Node.COMMENT_NODE -> {
val comment = document.createComment(node.nodeValue)
if (insideResources) root.appendChild(comment) else document.insertBefore(comment, root)
}
Node.ELEMENT_NODE -> {
val element = node as Element
when (element.tagName) {
"resources" -> walkChildren(element, appId, patchId, insideResources = true)
"app" -> {
val newAppId = element.getAttribute("id")
root.appendChild(document.createComment(" <app id=\"$newAppId\"> "))
walkChildren(element, newAppId, patchId, insideResources)
root.appendChild(document.createComment(" </app> "))
}
"patch" -> {
val newPatchId = element.getAttribute("id")
root.appendChild(document.createComment(" <patch id=\"$newPatchId\"> "))
walkChildren(element, appId, newPatchId, insideResources)
root.appendChild(document.createComment(" </patch> "))
}
"string" -> {
val name = element.getAttribute("name")
val value = element.textContent
val fullName = "$appId.$patchId.$name"
val stringElement = document.createElement("string")
stringElement.setAttribute("name", fullName)
stringElement.appendChild(document.createTextNode(value))
root.appendChild(stringElement)
}
else -> walkChildren(element, appId, patchId, insideResources)
}
}
}
}
builder.parse(stringsXmlFile).let {
val topLevel = it.childNodes
for (i in 0 until topLevel.length) {
val node = topLevel.item(i)
if (node != it.documentElement) walk(node)
}
walk(it.documentElement)
}
TransformerFactory.newInstance().newTransformer().apply {
setOutputProperty(OutputKeys.INDENT, "yes")
setOutputProperty(OutputKeys.ENCODING, "utf-8")
}.transform(DOMSource(document), StreamResult(stringsXmlFile))
}
}

View File

@@ -0,0 +1,52 @@
package app.revanced.patches.all.misc.screenshot
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.all.misc.transformation.transformInstructionsPatch
import app.revanced.util.getReference
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.reference.ImmutableMethodReference
import com.android.tools.smali.dexlib2.util.MethodUtil
private val registerScreenCaptureCallbackMethodReference = ImmutableMethodReference(
"Landroid/app/Activity;",
"registerScreenCaptureCallback",
listOf(
"Ljava/util/concurrent/Executor;",
"Landroid/app/Activity\$ScreenCaptureCallback;",
),
"V"
)
private val unregisterScreenCaptureCallbackMethodReference = ImmutableMethodReference(
"Landroid/app/Activity;",
"unregisterScreenCaptureCallback",
listOf(
"Landroid/app/Activity\$ScreenCaptureCallback;",
),
"V"
)
@Suppress("unused")
val preventScreenshotDetectionPatch = bytecodePatch(
name = "Prevent screenshot detection",
description = "Removes the registration of all screen capture callbacks. This prevents the app from detecting screenshots.",
use = false
) {
dependsOn(transformInstructionsPatch(
filterMap = { _, _, instruction, instructionIndex ->
if (instruction.opcode != Opcode.INVOKE_VIRTUAL) return@transformInstructionsPatch null
val reference = instruction.getReference<MethodReference>() ?: return@transformInstructionsPatch null
instructionIndex.takeIf {
MethodUtil.methodSignaturesMatch(reference, registerScreenCaptureCallbackMethodReference) ||
MethodUtil.methodSignaturesMatch(reference, unregisterScreenCaptureCallbackMethodReference)
}
},
transform = { mutableMethod, instructionIndex ->
mutableMethod.removeInstruction(instructionIndex)
}
))
}

View File

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

View File

@@ -5,24 +5,11 @@ import app.revanced.patches.music.misc.extension.sharedExtensionPatch
import app.revanced.patches.music.misc.settings.settingsPatch import app.revanced.patches.music.misc.settings.settingsPatch
import app.revanced.util.returnEarly import app.revanced.util.returnEarly
@Deprecated("This patch is useless by itself and has been merged into another patch.", ReplaceWith("unlockAndroidAutoMediaBrowserPatch"))
@Suppress("unused") @Suppress("unused")
val bypassCertificateChecksPatch = bytecodePatch( val bypassCertificateChecksPatch = bytecodePatch(
name = "Bypass certificate checks",
description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.", description = "Bypasses certificate checks which prevent YouTube Music from working on Android Auto.",
) { ) {
dependsOn( dependsOn(unlockAndroidAutoMediaBrowserPatch)
sharedExtensionPatch,
settingsPatch
)
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
)
)
execute {
checkCertificateFingerprint.method.returnEarly(true)
}
} }

View File

@@ -1,6 +1,5 @@
package app.revanced.patches.music.misc.androidauto package app.revanced.patches.music.misc.androidauto
import com.android.tools.smali.dexlib2.AccessFlags
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
internal val checkCertificateFingerprint = fingerprint { internal val checkCertificateFingerprint = fingerprint {
@@ -10,4 +9,13 @@ internal val checkCertificateFingerprint = fingerprint {
"X509", "X509",
"Failed to get certificate" // Partial String match. "Failed to get certificate" // Partial String match.
) )
}
internal val searchMediaItemsConstructorFingerprint = fingerprint {
returns("V")
strings("ytm_media_browser/search_media_items")
}
internal val searchMediaItemsExecuteFingerprint = fingerprint {
parameters()
} }

View File

@@ -0,0 +1,38 @@
package app.revanced.patches.music.misc.androidauto
import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.util.getReference
import app.revanced.util.registersUsed
import app.revanced.util.returnEarly
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
@Suppress("unused")
val unlockAndroidAutoMediaBrowserPatch = bytecodePatch(
name = "Unlock Android Auto Media Browser",
description = "Unlocks Android Auto Media Browser which enables the search function including speech to text.",
) {
compatibleWith(
"com.google.android.apps.youtube.music"(
"7.29.52",
"8.10.52"
)
)
execute {
checkCertificateFingerprint.method.returnEarly(true)
searchMediaItemsExecuteFingerprint
.match(searchMediaItemsConstructorFingerprint.classDef)
.method.apply {
val targetIndex = instructions.indexOfFirst {
it.opcode == Opcode.IGET_OBJECT && it.getReference<FieldReference>()?.type == "Ljava/lang/String;"
}
val register = instructions[targetIndex].registersUsed.first()
replaceInstruction(targetIndex, "const-string v$register, \"com.google.android.apps.youtube.music\"")
}
}
}

View File

@@ -7,20 +7,15 @@ import app.revanced.patches.shared.misc.debugging.enableDebuggingPatch
@Suppress("unused") @Suppress("unused")
val enableDebuggingPatch = enableDebuggingPatch( val enableDebuggingPatch = enableDebuggingPatch(
block = { sharedExtensionPatch = sharedExtensionPatch,
dependsOn( settingsPatch = settingsPatch,
sharedExtensionPatch, compatibleWithPackages = arrayOf(
settingsPatch, "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. // String feature flag does not appear to be present with YT Music.
hookStringFeatureFlag = false, hookStringFeatureFlag = false,
preferenceScreen = PreferenceScreen.MISC preferenceScreen = PreferenceScreen.MISC,
) )

View File

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

View File

@@ -11,3 +11,19 @@ internal val mainActivityOnCreateFingerprint = fingerprint {
method.name == "onCreate" && classDef.type == YOUTUBE_MUSIC_MAIN_ACTIVITY_CLASS_TYPE 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"
}
}

View File

@@ -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<Patch<*>> = emptySet(),
filterClasses: Set<String>,
vararg compatibleWithPackages: Pair<String, Set<String>?>,
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()
}
}

View File

@@ -2,23 +2,14 @@ package app.revanced.patches.shared.misc.debugging
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.BytecodePatchBuilder import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.patch.bytecodePatch import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch 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.*
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.PreferenceScreenPreference.Sorting import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference.Sorting
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.util.*
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 com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction 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. * Patch shared with YouTube and YT Music.
*/ */
internal fun enableDebuggingPatch( internal fun enableDebuggingPatch(
block: BytecodePatchBuilder.() -> Unit = {}, sharedExtensionPatch: Patch<*>,
executeBlock: BytecodePatchContext.() -> Unit = {}, settingsPatch: Patch<*>,
vararg compatibleWithPackages: Pair<String, Set<String>>,
hookStringFeatureFlag: Boolean, hookStringFeatureFlag: Boolean,
preferenceScreen: BasePreferenceScreen.Screen, preferenceScreen: BasePreferenceScreen.Screen,
additionalDebugPreferences: List<BasePreference> = emptyList()
) = bytecodePatch( ) = bytecodePatch(
name = "Enable debugging", name = "Enable debugging",
description = "Adds options for debugging and exporting ReVanced logs to the clipboard.", description = "Adds options for debugging and exporting ReVanced logs to the clipboard.",
) { ) {
compatibleWith(packages = compatibleWithPackages)
dependsOn( dependsOn(
sharedExtensionPatch,
settingsPatch,
addResourcesPatch, addResourcesPatch,
resourcePatch { resourcePatch {
execute { execute {
copyResources( copyResources(
"settings", "settings",
ResourceGroup("drawable", ResourceGroup(
"drawable",
// Action buttons. // Action buttons.
"revanced_settings_copy_all.xml", "revanced_settings_copy_all.xml",
"revanced_settings_deselect_all.xml", "revanced_settings_deselect_all.xml",
@@ -61,38 +56,29 @@ internal fun enableDebuggingPatch(
} }
) )
block()
execute { execute {
executeBlock()
addResources("shared", "misc.debugging.enableDebuggingPatch") addResources("shared", "misc.debugging.enableDebuggingPatch")
val preferences = mutableSetOf<BasePreference>( val preferences = setOf(
SwitchPreference("revanced_debug"), SwitchPreference("revanced_debug"),
) SwitchPreference("revanced_debug_protobuffer"),
SwitchPreference("revanced_debug_stacktrace"),
preferences.addAll(additionalDebugPreferences) SwitchPreference("revanced_debug_toast_on_error"),
NonInteractivePreference(
preferences.addAll( "revanced_debug_export_logs_to_clipboard",
listOf( tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference",
SwitchPreference("revanced_debug_stacktrace"), selectable = true
SwitchPreference("revanced_debug_toast_on_error"), ),
NonInteractivePreference( NonInteractivePreference(
"revanced_debug_export_logs_to_clipboard", "revanced_debug_logs_clear_buffer",
tag = "app.revanced.extension.shared.settings.preference.ExportLogToClipboardPreference", tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference",
selectable = true selectable = true
), ),
NonInteractivePreference( NonInteractivePreference(
"revanced_debug_logs_clear_buffer", "revanced_debug_feature_flags_manager",
tag = "app.revanced.extension.shared.settings.preference.ClearLogBufferPreference", tag = "app.revanced.extension.shared.settings.preference.FeatureFlagsManagerPreference",
selectable = true selectable = true
),
NonInteractivePreference(
"revanced_debug_feature_flags_manager",
tag = "app.revanced.extension.shared.settings.preference.FeatureFlagsManagerPreference",
selectable = true
)
) )
) )

View File

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

View File

@@ -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-><init>()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<FieldReference>()
reference?.definingClass == conversionContextFingerprintToString.originalClassDef.type
&& reference.type == "Ljava/lang/String;"
}
it.method.getInstruction<ReferenceInstruction>(index).getReference<FieldReference>()!!
}
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()
}

View File

@@ -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.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.all.misc.resources.addResourcesPatch
import app.revanced.patches.shared.misc.fix.verticalscroll.verticalScrollPatch 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.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.ad.getpremium.hideGetPremiumPatch import app.revanced.patches.youtube.ad.getpremium.hideGetPremiumPatch
import app.revanced.patches.youtube.misc.fix.backtoexitgesture.fixBackToExitGesturePatch 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.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch

View File

@@ -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.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.settings.preference.PreferenceScreenPreference 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.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.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen

View File

@@ -9,34 +9,27 @@ import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.instructions import app.revanced.patcher.extensions.InstructionExtensions.instructions
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.smali.ExternalLabel import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.all.misc.resources.addResources 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.get
import app.revanced.patches.shared.misc.mapping.resourceMappingPatch import app.revanced.patches.shared.misc.mapping.resourceMappingPatch
import app.revanced.patches.shared.misc.mapping.resourceMappings import app.revanced.patches.shared.misc.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.* 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.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch 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_07_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_09_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.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
import app.revanced.util.findFreeRegister import app.revanced.util.*
import app.revanced.util.findInstructionIndicesReversedOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method 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.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction 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.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
var expandButtonDownId = -1L var expandButtonDownId = -1L
private set private set
@@ -108,184 +101,170 @@ private const val DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME =
private const val COMMENTS_FILTER_CLASS_NAME = private const val COMMENTS_FILTER_CLASS_NAME =
"Lapp/revanced/extension/youtube/patches/components/CommentsFilter;" "Lapp/revanced/extension/youtube/patches/components/CommentsFilter;"
private const val CUSTOM_FILTER_CLASS_NAME = 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 = private const val KEYWORD_FILTER_CLASS_NAME =
"Lapp/revanced/extension/youtube/patches/components/KeywordContentFilter;" "Lapp/revanced/extension/youtube/patches/components/KeywordContentFilter;"
val hideLayoutComponentsPatch = bytecodePatch(
name = "Hide layout components",
description = "Adds options to hide general layout components.",
) { val hideLayoutComponentsPatch = hideLayoutComponentsPatch(
dependsOn( lithoFilterPatch = lithoFilterPatch,
lithoFilterPatch, settingsPatch = settingsPatch,
settingsPatch, additionalDependencies = setOf(
addResourcesPatch,
hideLayoutComponentsResourcePatch, hideLayoutComponentsResourcePatch,
navigationBarHookPatch, navigationBarHookPatch,
) ),
filterClasses = setOf(
compatibleWith( LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR,
"com.google.android.youtube"( 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", "19.34.42",
"20.07.39", "20.07.39",
"20.13.41", "20.13.41",
"20.14.43", "20.14.43",
) )
) )
) {
addResources("youtube", "layout.hide.general.hideLayoutComponentsPatch")
execute { PreferenceScreen.PLAYER.addPreferences(
addResources("youtube", "layout.hide.general.hideLayoutComponentsPatch") 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( PreferenceScreen.FEED.addPreferences(
PreferenceScreenPreference( PreferenceScreenPreference(
key = "revanced_hide_description_components_screen", key = "revanced_hide_keyword_content_screen",
preferences = setOf( sorting = PreferenceScreenPreference.Sorting.UNSORTED,
SwitchPreference("revanced_hide_ai_generated_video_summary_section"), preferences = setOf(
SwitchPreference("revanced_hide_ask_section"), SwitchPreference("revanced_hide_keyword_content_home"),
SwitchPreference("revanced_hide_attributes_section"), SwitchPreference("revanced_hide_keyword_content_subscriptions"),
SwitchPreference("revanced_hide_chapters_section"), SwitchPreference("revanced_hide_keyword_content_search"),
SwitchPreference("revanced_hide_featured_links_section"), TextPreference("revanced_hide_keyword_content_phrases", inputType = InputType.TEXT_MULTI_LINE),
SwitchPreference("revanced_hide_featured_videos_section"), NonInteractivePreference(
SwitchPreference("revanced_hide_info_cards_section"), key = "revanced_hide_keyword_content_about",
SwitchPreference("revanced_hide_how_this_was_made_section"), tag = "app.revanced.extension.shared.settings.preference.BulletPointPreference"
SwitchPreference("revanced_hide_hype_points"), ),
SwitchPreference("revanced_hide_key_concepts_section"), NonInteractivePreference(
SwitchPreference("revanced_hide_podcast_section"), key = "revanced_hide_keyword_content_about_whole_words",
SwitchPreference("revanced_hide_subscribe_button"), tag = "app.revanced.extension.youtube.settings.preference.HtmlPreference",
SwitchPreference("revanced_hide_transcript_section"),
), ),
), ),
PreferenceScreenPreference( ),
"revanced_comments_screen", PreferenceScreenPreference(
preferences = setOf( key = "revanced_hide_filter_bar_screen",
SwitchPreference("revanced_hide_comments_ai_chat_summary"), preferences = setOf(
SwitchPreference("revanced_hide_comments_ai_summary"), SwitchPreference("revanced_hide_filter_bar_feed_in_feed"),
SwitchPreference("revanced_hide_comments_channel_guidelines"), SwitchPreference("revanced_hide_filter_bar_feed_in_related_videos"),
SwitchPreference("revanced_hide_comments_by_members_header"), SwitchPreference("revanced_hide_filter_bar_feed_in_search"),
SwitchPreference("revanced_hide_comments_section"), SwitchPreference("revanced_hide_filter_bar_feed_in_history"),
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"), PreferenceScreenPreference(
SwitchPreference("revanced_hide_crowdfunding_box"), key = "revanced_channel_screen",
SwitchPreference("revanced_hide_emergency_box"), preferences = setOf(
SwitchPreference("revanced_hide_info_panels"), SwitchPreference("revanced_hide_community_button"),
SwitchPreference("revanced_hide_join_membership_button"), SwitchPreference("revanced_hide_for_you_shelf"),
SwitchPreference("revanced_hide_medical_panels"), SwitchPreference("revanced_hide_join_button"),
SwitchPreference("revanced_hide_quick_actions"), SwitchPreference("revanced_hide_links_preview"),
SwitchPreference("revanced_hide_related_videos"), SwitchPreference("revanced_hide_members_shelf"),
SwitchPreference("revanced_hide_subscribers_community_guidelines"), SwitchPreference("revanced_hide_store_button"),
SwitchPreference("revanced_hide_timed_reactions"), SwitchPreference("revanced_hide_subscribe_button_in_channel_page"),
)
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_hide_filter_bar_screen", SwitchPreference("revanced_hide_album_cards"),
preferences = setOf( SwitchPreference("revanced_hide_artist_cards"),
SwitchPreference("revanced_hide_filter_bar_feed_in_feed"), SwitchPreference("revanced_hide_chips_shelf"),
SwitchPreference("revanced_hide_filter_bar_feed_in_related_videos"), SwitchPreference("revanced_hide_community_posts"),
SwitchPreference("revanced_hide_filter_bar_feed_in_search"), SwitchPreference("revanced_hide_compact_banner"),
SwitchPreference("revanced_hide_filter_bar_feed_in_history"), SwitchPreference("revanced_hide_expandable_card"),
), SwitchPreference("revanced_hide_floating_microphone_button"),
), SwitchPreference(
PreferenceScreenPreference( key = "revanced_hide_horizontal_shelves",
key = "revanced_channel_screen", tag = "app.revanced.extension.shared.settings.preference.BulletPointSwitchPreference"
preferences = setOf( ),
SwitchPreference("revanced_hide_community_button"), SwitchPreference("revanced_hide_image_shelf"),
SwitchPreference("revanced_hide_for_you_shelf"), SwitchPreference("revanced_hide_latest_posts"),
SwitchPreference("revanced_hide_join_button"), SwitchPreference("revanced_hide_mix_playlists"),
SwitchPreference("revanced_hide_links_preview"), SwitchPreference("revanced_hide_movies_section"),
SwitchPreference("revanced_hide_members_shelf"), SwitchPreference("revanced_hide_notify_me_button"),
SwitchPreference("revanced_hide_store_button"), SwitchPreference("revanced_hide_playables"),
SwitchPreference("revanced_hide_subscribe_button_in_channel_page"), SwitchPreference("revanced_hide_show_more_button"),
), SwitchPreference("revanced_hide_surveys"),
), SwitchPreference("revanced_hide_ticket_shelf"),
SwitchPreference("revanced_hide_album_cards"), SwitchPreference("revanced_hide_upload_time"),
SwitchPreference("revanced_hide_artist_cards"), SwitchPreference("revanced_hide_video_recommendation_labels"),
SwitchPreference("revanced_hide_chips_shelf"), SwitchPreference("revanced_hide_view_count"),
SwitchPreference("revanced_hide_community_posts"), SwitchPreference("revanced_hide_visual_spacer"),
SwitchPreference("revanced_hide_compact_banner"), SwitchPreference("revanced_hide_doodles"),
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( // region Mix playlists
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),
),
),
)
addLithoFilter(LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR) (if (is_20_09_or_greater) parseElementFromBufferFingerprint
addLithoFilter(DESCRIPTION_COMPONENTS_FILTER_CLASS_NAME) else if (is_20_07_or_greater) parseElementFromBufferLegacy2007Fingerprint
addLithoFilter(COMMENTS_FILTER_CLASS_NAME) else parseElementFromBufferLegacy1901Fingerprint).let {
addLithoFilter(KEYWORD_FILTER_CLASS_NAME) it.method.apply {
addLithoFilter(CUSTOM_FILTER_CLASS_NAME) val byteArrayParameter = "p3"
val startIndex = it.patternMatch!!.startIndex
val conversionContextRegister = getInstruction<TwoRegisterInstruction>(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 addInstructionsWithLabels(
insertIndex,
(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<TwoRegisterInstruction>(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,
"""
invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z invoke-static { v$conversionContextRegister, $byteArrayParameter }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->filterMixPlaylists(Ljava/lang/Object;[B)Z
move-result v$freeRegister move-result v$freeRegister
if-eqz v$freeRegister, :show if-eqz v$freeRegister, :show
@@ -294,193 +273,192 @@ val hideLayoutComponentsPatch = bytecodePatch(
:show :show
nop 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( showWatermarkFingerprint.match(
playerOverlayFingerprint.originalClassDef, playerOverlayFingerprint.originalClassDef,
).method.apply { ).method.apply {
val index = implementation!!.instructions.size - 5 val index = implementation!!.instructions.size - 5
removeInstruction(index) removeInstruction(index)
addInstructions( addInstructions(
index, index,
""" """
invoke-static {}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->showWatermark()Z invoke-static {}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->showWatermark()Z
move-result p2 move-result p2
""", """,
) )
} }
// endregion // endregion
// region Show more button // region Show more button
hideShowMoreButtonFingerprint.method.apply { hideShowMoreButtonFingerprint.method.apply {
val moveRegisterIndex = hideShowMoreButtonFingerprint.patternMatch!!.endIndex val moveRegisterIndex = hideShowMoreButtonFingerprint.patternMatch!!.endIndex
val viewRegister = getInstruction<OneRegisterInstruction>(moveRegisterIndex).registerA val viewRegister = getInstruction<OneRegisterInstruction>(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<TwoRegisterInstruction>(insertIndex).registerA
val insertIndex = moveRegisterIndex + 1
addInstruction( addInstruction(
insertIndex, insertIndex,
"invoke-static { v$viewRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" + "invoke-static {v$objectRegister}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
"->hideShowMoreButton(Landroid/view/View;)V", "->hideCrowdfundingBox(Landroid/view/View;)V",
) )
} }
}
// endregion // endregion
// region crowdfunding box // region hide album cards
crowdfundingBoxFingerprint.let {
it.method.apply {
val insertIndex = it.patternMatch!!.endIndex
val objectRegister = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
addInstruction( albumCardsFingerprint.let {
insertIndex, it.method.apply {
"invoke-static {v$objectRegister}, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" + val checkCastAnchorIndex = it.patternMatch!!.endIndex
"->hideCrowdfundingBox(Landroid/view/View;)V", val insertIndex = checkCastAnchorIndex + 1
) val register = getInstruction<OneRegisterInstruction>(checkCastAnchorIndex).registerA
}
}
// endregion addInstruction(
insertIndex,
// region hide album cards "invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
albumCardsFingerprint.let {
it.method.apply {
val checkCastAnchorIndex = it.patternMatch!!.endIndex
val insertIndex = checkCastAnchorIndex + 1
val register = getInstruction<OneRegisterInstruction>(checkCastAnchorIndex).registerA
addInstruction(
insertIndex,
"invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR" +
"->hideAlbumCard(Landroid/view/View;)V", "->hideAlbumCard(Landroid/view/View;)V",
) )
}
} }
}
// endregion // endregion
// region hide floating microphone // region hide floating microphone
showFloatingMicrophoneButtonFingerprint.method.apply { showFloatingMicrophoneButtonFingerprint.method.apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(fabButtonId) val literalIndex = indexOfFirstLiteralInstructionOrThrow(fabButtonId)
val booleanIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.IGET_BOOLEAN) val booleanIndex = indexOfFirstInstructionOrThrow(literalIndex, Opcode.IGET_BOOLEAN)
val register = getInstruction<TwoRegisterInstruction>(booleanIndex).registerA val register = getInstruction<TwoRegisterInstruction>(booleanIndex).registerA
addInstructions( addInstructions(
booleanIndex + 1, booleanIndex + 1,
""" """
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideFloatingMicrophoneButton(Z)Z invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideFloatingMicrophoneButton(Z)Z
move-result v$register move-result v$register
""" """
)
}
// endregion
// region 'Yoodles'
yoodlesImageViewFingerprint.method.apply {
findInstructionIndicesReversedOrThrow {
getReference<MethodReference>()?.name == "setImageDrawable"
}.forEach { insertIndex ->
val drawableRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
val imageViewRegister = getInstruction<FiveRegisterInstruction>(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 { // region hide view count
findInstructionIndicesReversedOrThrow {
getReference<MethodReference>()?.name == "setImageDrawable"
}.forEach { insertIndex ->
val drawableRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
val imageViewRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerC
replaceInstruction( hideViewCountFingerprint.method.apply {
insertIndex, val startIndex = hideViewCountFingerprint.patternMatch!!.startIndex
"invoke-static { v$imageViewRegister, v$drawableRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->" + var returnStringRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
"setDoodleDrawable(Landroid/widget/ImageView;Landroid/graphics/drawable/Drawable;)V"
) // Find the instruction where the text dimension is retrieved.
} val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow {
val reference = getReference<MethodReference>()
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<OneRegisterInstruction>(
applyDimensionIndex + 1
).registerA
addInstructions(
// region hide view count applyDimensionIndex - 1,
"""
hideViewCountFingerprint.method.apply {
val startIndex = hideViewCountFingerprint.patternMatch!!.startIndex
var returnStringRegister = getInstruction<OneRegisterInstruction>(startIndex).registerA
// Find the instruction where the text dimension is retrieved.
val applyDimensionIndex = indexOfFirstInstructionReversedOrThrow {
val reference = getReference<MethodReference>()
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<OneRegisterInstruction>(
applyDimensionIndex + 1
).registerA
addInstructions(
applyDimensionIndex - 1,
"""
invoke-static { v$returnStringRegister, v$floatDimensionRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->modifyFeedSubtitleSpan(Landroid/text/SpannableString;F)Landroid/text/SpannableString; invoke-static { v$returnStringRegister, v$floatDimensionRegister }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->modifyFeedSubtitleSpan(Landroid/text/SpannableString;F)Landroid/text/SpannableString;
move-result-object v$returnStringRegister move-result-object v$returnStringRegister
""" """
) )
} }
// endregion // endregion
// region hide filter bar // region hide filter bar
/** /**
* Patch a [Method] with a given [instructions]. * Patch a [Method] with a given [instructions].
* *
* @param RegisterInstruction The type of instruction to get the register from. * @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 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 hookRegisterOffset The offset to add to the register of the hook.
* @param instructions The instructions to add with the register as a parameter. * @param instructions The instructions to add with the register as a parameter.
*/ */
fun <RegisterInstruction : OneRegisterInstruction> Fingerprint.patch( fun <RegisterInstruction : OneRegisterInstruction> Fingerprint.patch(
insertIndexOffset: Int = 0, insertIndexOffset: Int = 0,
hookRegisterOffset: Int = 0, hookRegisterOffset: Int = 0,
instructions: (Int) -> String, instructions: (Int) -> String,
) = method.apply { ) = method.apply {
val endIndex = patternMatch!!.endIndex val endIndex = patternMatch!!.endIndex
val insertIndex = endIndex + insertIndexOffset val insertIndex = endIndex + insertIndexOffset
val register = val register =
getInstruction<RegisterInstruction>(endIndex + hookRegisterOffset).registerA getInstruction<RegisterInstruction>(endIndex + hookRegisterOffset).registerA
addInstructions(insertIndex, instructions(register)) addInstructions(insertIndex, instructions(register))
} }
filterBarHeightFingerprint.patch<TwoRegisterInstruction> { register -> filterBarHeightFingerprint.patch<TwoRegisterInstruction> { register ->
""" """
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInFeed(I)I invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInFeed(I)I
move-result v$register move-result v$register
""" """
} }
searchResultsChipBarFingerprint.patch<OneRegisterInstruction>(-1, -2) { register -> searchResultsChipBarFingerprint.patch<OneRegisterInstruction>(-1, -2) { register ->
""" """
invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInSearch(I)I invoke-static { v$register }, $LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInSearch(I)I
move-result v$register move-result v$register
""" """
} }
relatedChipCloudFingerprint.patch<OneRegisterInstruction>(1) { register -> relatedChipCloudFingerprint.patch<OneRegisterInstruction>(1) { register ->
"invoke-static { v$register }, " + "invoke-static { v$register }, " +
"$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V" "$LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR->hideInRelatedVideos(Landroid/view/View;)V"
}
} }
} }

View File

@@ -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.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch 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.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch

View File

@@ -5,7 +5,7 @@ import app.revanced.patches.all.misc.resources.addResources
import app.revanced.patches.all.misc.resources.addResourcesPatch 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.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference 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.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.settings.PreferenceScreen import app.revanced.patches.youtube.misc.settings.PreferenceScreen

View File

@@ -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.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch 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.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch import app.revanced.patches.youtube.misc.navigation.navigationBarHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_41_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_41_or_greater

View File

@@ -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.PreferenceScreenPreference
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch 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.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch
import app.revanced.patches.youtube.misc.playservice.is_19_33_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_33_or_greater

View File

@@ -1,33 +1,22 @@
package app.revanced.patches.youtube.misc.debugging 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.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.PreferenceScreen
import app.revanced.patches.youtube.misc.settings.settingsPatch import app.revanced.patches.youtube.misc.settings.settingsPatch
@Suppress("unused") @Suppress("unused")
val enableDebuggingPatch = enableDebuggingPatch( val enableDebuggingPatch = enableDebuggingPatch(
block = { sharedExtensionPatch = sharedExtensionPatch,
dependsOn( settingsPatch = settingsPatch,
sharedExtensionPatch, compatibleWithPackages = arrayOf(
settingsPatch, "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, hookStringFeatureFlag = true,
preferenceScreen = PreferenceScreen.MISC, preferenceScreen = PreferenceScreen.MISC,
additionalDebugPreferences = listOf(SwitchPreference("revanced_debug_protobuffer"))
) )

View File

@@ -1,58 +1,8 @@
package app.revanced.patches.youtube.misc.litho.filter package app.revanced.patches.youtube.misc.litho.filter
import app.revanced.patcher.fingerprint import app.revanced.patcher.fingerprint
import app.revanced.util.containsLiteralInstruction
import app.revanced.util.literal import app.revanced.util.literal
import com.android.tools.smali.dexlib2.AccessFlags 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 { internal val lithoComponentNameUpbFeatureFlagFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)

View File

@@ -3,225 +3,68 @@
package app.revanced.patches.youtube.misc.litho.filter package app.revanced.patches.youtube.misc.litho.filter
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction 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.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstructions import app.revanced.patcher.patch.BytecodePatchContext
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction import app.revanced.patches.shared.misc.litho.filter.lithoFilterPatch
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch
import app.revanced.patches.youtube.misc.playservice.is_19_17_or_greater 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_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.is_20_05_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.misc.playservice.versionCheckPatch
import app.revanced.patches.youtube.shared.conversionContextFingerprintToString 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.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.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction 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 lateinit var addLithoFilter: (String) -> Unit
private set private set
private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/components/LithoFilterPatch;" val lithoFilterPatch = lithoFilterPatch(
componentCreateInsertionIndex = {
val lithoFilterPatch = bytecodePatch( if (is_19_17_or_greater) {
description = "Hooks the method which parses the bytes into a ComponentContext to filter components.", indexOfFirstInstructionOrThrow(Opcode.RETURN_OBJECT)
} else {
// 19.16 clobbers p2 so must check at start of the method
0
}
},
conversionContextFingerprintToString = conversionContextFingerprintToString,
executeBlock = BytecodePatchContext::executeBlock,
) { ) {
dependsOn( dependsOn(versionCheckPatch)
sharedExtensionPatch, }
versionCheckPatch,
) private fun BytecodePatchContext.executeBlock() {
// region A/B test of new Litho native code.
var filterCount = 0
// 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.
* 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. // Flag was removed in 20.05. It appears a new flag might be used instead (45660109L),
* The pathBuilder is used to filter components by their path. // but if the flag is forced on then litho filtering still works correctly.
* if (is_19_25_or_greater && !is_20_05_or_greater) {
* Additionally, the method contains a reference to the component's identifier. lithoComponentNameUpbFeatureFlagFingerprint.method.apply {
* The identifier is used to filter components by their identifier. // Don't use return early, so the debug patch logs if this was originally on.
* val insertIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN)
* The protobuf buffer is passed along from a different injection point before the filtering occurs. val register = getInstruction<OneRegisterInstruction>(insertIndex).registerA
* The buffer is a large byte array that represents the component tree.
* This byte array is searched for strings that indicate the current component. addInstruction(insertIndex, "const/4 v$register, 0x0")
* }
* 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.
* // Turn off a feature flag that enables native code of protobuf parsing (Upb protobuf).
* The following pseudocode shows how this patch works: // 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.
* class SomeOtherClass { lithoConverterBufferUpbFeatureFlagFingerprint.method.apply {
* // Called before ComponentContextParser.parseComponent() method. val index = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT)
* public void someOtherMethod(ByteBuffer byteBuffer) { val register = getInstruction<OneRegisterInstruction>(index).registerA
* ExtensionClass.setProtoBuffer(byteBuffer); // Inserted by this patch.
* ... addInstruction(index + 1, "const/4 v$register, 0x0")
* } }
* }
* // endregion
* class CreateComponentClass {
* public Component createComponent() { // 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
* 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-><init>()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<FieldReference>()
reference?.definingClass == conversionContextFingerprintToString.originalClassDef.type
&& reference.type == "Ljava/lang/String;"
}
it.method.getInstruction<ReferenceInstruction>(index).getReference<FieldReference>()!!
}
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<OneRegisterInstruction>(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<OneRegisterInstruction>(index).registerA
addInstruction(index + 1, "const/4 v$register, 0x0")
}
// endregion
}
finalize {
lithoFilterFingerprint.method.replaceInstruction(0, "const/16 v0, $filterCount")
}
} }

View File

@@ -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.mapping.resourceMappings
import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.shared.misc.settings.preference.SwitchPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch 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.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook import app.revanced.patches.youtube.misc.recyclerviewtree.hook.addRecyclerViewTreeHook
import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch import app.revanced.patches.youtube.misc.recyclerviewtree.hook.recyclerViewTreeHookPatch

View File

@@ -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.SwitchPreference
import app.revanced.patches.shared.misc.settings.preference.TextPreference import app.revanced.patches.shared.misc.settings.preference.TextPreference
import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch 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.litho.filter.lithoFilterPatch
import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater import app.revanced.patches.youtube.misc.playservice.is_19_25_or_greater
import app.revanced.patches.youtube.misc.playservice.versionCheckPatch import app.revanced.patches.youtube.misc.playservice.versionCheckPatch

View File

@@ -1,40 +0,0 @@
package app.revanced.util
import java.io.File
/**
* Comments out the non-standard <app> and <patch> tags.
*
* Previously this was done on Crowdin after pushing.
* But Crowdin preprocessing has randomly failed but still used the unmodified
* strings.xml file, which effectively deletes all patch strings from Crowdin.
*/
internal fun main(args: Array<String>) {
if (args.size != 2) {
throw RuntimeException("Exactly two arguments are required: <input_file> <output_file>")
}
val inputFilePath = args[0]
val inputFile = File(inputFilePath)
if (!inputFile.exists()) {
throw RuntimeException(
"Input file not found: $inputFilePath currentDirectory: " + File(".").canonicalPath
)
}
// Comment out the non-standard tags. Otherwise Crowdin interprets the file
// not as Android but instead a generic xml file where strings are
// identified by xml position and not key.
val content = inputFile.readText()
val tagRegex = """((<app\s+.*>)|(</app>)|(<patch\s+.*>)|(</patch>))""".toRegex()
val modifiedContent = content.replace(tagRegex, """<!-- $1 -->""")
// Write modified content to the output file (creates file if it doesn't exist).
val outputFilePath = args[1]
val outputFile = File(outputFilePath)
outputFile.parentFile?.mkdirs()
outputFile.writeText(modifiedContent)
println("Preprocessed strings.xml to: $outputFilePath")
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -224,12 +224,14 @@ Second \"item\" text"</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch> </patch>
<patch id="misc.announcements.announcementsPatch"> <patch id="misc.announcements.announcementsPatch">
<string name="revanced_announcements_dialog_dismiss">খাৰিজ কৰক</string> <string name="youtube.misc.announcements.announcementsPatch.revanced_announcements_dialog_dismiss">খাৰিজ কৰক</string>
</patch> </patch>
<patch id="misc.loopvideo.loopVideoPatch"> <patch id="misc.loopvideo.loopVideoPatch">
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -21,60 +21,60 @@ Second \"item\" text"</string>
<resources> <resources>
<app id="shared"> <app id="shared">
<patch id="layout.branding.baseCustomBrandingPatch"> <patch id="layout.branding.baseCustomBrandingPatch">
<string name="revanced_custom_branding_name_title">نام برنامه</string> <string name="shared.layout.branding.baseCustomBrandingPatch.revanced_custom_branding_name_title">نام برنامه</string>
<!-- Translations of this should be identical to revanced_custom_branding_icon_entry_5 --> <!-- Translations of this should be identical to revanced_custom_branding_icon_entry_5 -->
<string name="revanced_custom_branding_icon_title">آیکون برنامه</string> <string name="shared.layout.branding.baseCustomBrandingPatch.revanced_custom_branding_icon_title">آیکون برنامه</string>
<!-- Translation of this should be identical to revanced_header_logo_entry_5 --> <!-- Translation of this should be identical to revanced_header_logo_entry_5 -->
<!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 --> <!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 -->
</patch> </patch>
<patch id="misc.checks.checkEnvironmentPatch"> <patch id="misc.checks.checkEnvironmentPatch">
<string name="revanced_check_environment_failed_title">بررسی ناموفق بود</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_title">بررسی ناموفق بود</string>
<string name="revanced_check_environment_dialog_open_official_source_button">رفتن به وبسایت رسمی</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_dialog_open_official_source_button">رفتن به وبسایت رسمی</string>
<string name="revanced_check_environment_dialog_ignore_button">نادیده بگیر</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_dialog_ignore_button">نادیده بگیر</string>
<string name="revanced_check_environment_failed_message">&lt;h5&gt;به نظر نمی‌رسد این برنامه توسط شما وصله شده باشد.&lt;/h5&gt;&lt;br&gt;این برنامه ممکن است به درستی کار نکند، &lt;b&gt;ممکن است استفاده از آن مضر یا حتی خطرناک باشد&lt;/b&gt;.&lt;br&gt;&lt;بر&gt;این برنامه از قبل دریافت شده است یا این چک از قبل دریافت شده است else:&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;اکیداً توصیه می‌شود که &lt;b&gt;این برنامه را حذف نصب کنید و خودتان آن را وصله کنید&lt;/b&gt; برای اطمینان از اینکه از یک برنامه معتبر و ایمن استفاده می‌کنید.&lt;p&gt;&lt;br&gt;اگر نادیده گرفته شود، این هشدار فقط دو بار نشان داده می‌شود.</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_message">&lt;h5&gt;به نظر نمی‌رسد این برنامه توسط شما وصله شده باشد.&lt;/h5&gt;&lt;br&gt;این برنامه ممکن است به درستی کار نکند، &lt;b&gt;ممکن است استفاده از آن مضر یا حتی خطرناک باشد&lt;/b&gt;.&lt;br&gt;&lt;بر&gt;این برنامه از قبل دریافت شده است یا این چک از قبل دریافت شده است else:&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;اکیداً توصیه می‌شود که &lt;b&gt;این برنامه را حذف نصب کنید و خودتان آن را وصله کنید&lt;/b&gt; برای اطمینان از اینکه از یک برنامه معتبر و ایمن استفاده می‌کنید.&lt;p&gt;&lt;br&gt;اگر نادیده گرفته شود، این هشدار فقط دو بار نشان داده می‌شود.</string>
<string name="revanced_check_environment_not_same_patching_device">روی دستگاه دیگری وصله شده است</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_same_patching_device">روی دستگاه دیگری وصله شده است</string>
<string name="revanced_check_environment_manager_not_expected_installer">به وسیله ReVanced Manager نصب نشده است</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_manager_not_expected_installer">به وسیله ReVanced Manager نصب نشده است</string>
<string name="revanced_check_environment_not_near_patch_time">بیشتر از ۱۰ دقیقه پیش وصله شده است</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time">بیشتر از ۱۰ دقیقه پیش وصله شده است</string>
<string name="revanced_check_environment_not_near_patch_time_days">%s روز پیش وصله شده است</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time_days">%s روز پیش وصله شده است</string>
<string name="revanced_check_environment_not_near_patch_time_invalid">تاریخ ایجاد APK مخدوش شده است</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time_invalid">تاریخ ایجاد APK مخدوش شده است</string>
</patch> </patch>
<patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch"> <patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch">
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<string name="revanced_settings_submenu_title">تنظیمات</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_submenu_title">تنظیمات</string>
<string name="revanced_settings_reset">بازنشانی</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_reset">بازنشانی</string>
<string name="revanced_settings_restart">راه‌اندازی مجدد</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_restart">راه‌اندازی مجدد</string>
<string name="revanced_settings_import">واردکردن</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import">واردکردن</string>
<string name="revanced_settings_import_copy">رونوشت‌</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import_copy">رونوشت‌</string>
<string name="revanced_settings_import_reset">بازگرداندن تنظیمات ReVanced به پیش‌فرض</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import_reset">بازگرداندن تنظیمات ReVanced به پیش‌فرض</string>
<string name="revanced_settings_import_success">%d تنظیمات وارد شدند</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import_success">%d تنظیمات وارد شدند</string>
<string name="revanced_settings_import_failure_parse">واردکردن انجام نشد: %s</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_import_failure_parse">واردکردن انجام نشد: %s</string>
<string name="revanced_settings_search_hint">تنظیمات جستجو</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_hint">تنظیمات جستجو</string>
<string name="revanced_settings_search_no_results_title">نتایجی برای %s یافت نشد</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_no_results_title">نتایجی برای %s یافت نشد</string>
<string name="revanced_settings_search_no_results_summary">کلیدواژه دیگری را امتحان کنید</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_no_results_summary">کلیدواژه دیگری را امتحان کنید</string>
<string name="revanced_settings_search_remove_message">حذف از تاریخچه جستجو؟</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_remove_message">حذف از تاریخچه جستجو؟</string>
<string name="revanced_settings_search_empty_history_title">تاریخچه جستجو خالی است</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_empty_history_title">تاریخچه جستجو خالی است</string>
<string name="revanced_settings_search_history_title">نمایش تاریخچه جستجوی تنظیمات</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_search_history_title">نمایش تاریخچه جستجوی تنظیمات</string>
<string name="revanced_show_menu_icons_title">نمایش آیکون تنظیمات ReVanced</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_show_menu_icons_title">نمایش آیکون تنظیمات ReVanced</string>
<string name="revanced_show_menu_icons_summary_on">نمادهای تنظیمات نشان داده می‌شوند</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_show_menu_icons_summary_on">نمادهای تنظیمات نشان داده می‌شوند</string>
<string name="revanced_show_menu_icons_summary_off">نمادهای تنظیمات نمایش داده نمی شوند</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_show_menu_icons_summary_off">نمادهای تنظیمات نمایش داده نمی شوند</string>
<string name="revanced_language_title">زبان ReVanced</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_language_title">زبان ReVanced</string>
<string name="revanced_language_DEFAULT">زبان برنامه</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_language_DEFAULT">زبان برنامه</string>
<string name="revanced_pref_import_export_title">وارد کردن/صادر کردن</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_pref_import_export_title">وارد کردن/صادر کردن</string>
<string name="revanced_pref_import_export_summary">وارد کردن / صادر کردن تنظیمات ReVanced</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_pref_import_export_summary">وارد کردن / صادر کردن تنظیمات ReVanced</string>
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<string name="revanced_settings_about_links_body">شما درحال استفاده از نسخه &lt;i&gt;%s&lt;/i&gt; از پچ Revanced هستید</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_about_links_body">شما درحال استفاده از نسخه &lt;i&gt;%s&lt;/i&gt; از پچ Revanced هستید</string>
<string name="revanced_settings_about_links_dev_header">توجه</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_about_links_dev_header">توجه</string>
<string name="revanced_settings_about_links_header">لینک‌های رسمی</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_about_links_header">لینک‌های رسمی</string>
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code, <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. --> and changes made here must also be made there. -->
</patch> </patch>
<patch id="misc.gms.gmsCoreSupportResourcePatch"> <patch id="misc.gms.gmsCoreSupportResourcePatch">
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
<string name="gms_core_toast_not_installed_message">MicroG GmsCore نصب نشده است. آنرا نصب کنید.</string> <string name="shared.misc.gms.gmsCoreSupportResourcePatch.gms_core_toast_not_installed_message">MicroG GmsCore نصب نشده است. آنرا نصب کنید.</string>
<string name="gms_core_dialog_title">اقدام لازم است</string> <string name="shared.misc.gms.gmsCoreSupportResourcePatch.gms_core_dialog_title">اقدام لازم است</string>
<string name="gms_core_dialog_open_website_text">باز کردن تارنما</string> <string name="shared.misc.gms.gmsCoreSupportResourcePatch.gms_core_dialog_open_website_text">باز کردن تارنما</string>
<string name="gms_core_dialog_continue_text">ادامه</string> <string name="shared.misc.gms.gmsCoreSupportResourcePatch.gms_core_dialog_continue_text">ادامه</string>
</patch> </patch>
<patch id="misc.fix.playback.spoofVideoStreamsPatch"> <patch id="misc.fix.playback.spoofVideoStreamsPatch">
</patch> </patch>
@@ -82,22 +82,22 @@ Second \"item\" text"</string>
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. --> <!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
</patch> </patch>
<patch id="misc.debugging.enableDebuggingPatch"> <patch id="misc.debugging.enableDebuggingPatch">
<string name="revanced_debug_screen_title">عیب‌یابی</string> <string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_screen_title">عیب‌یابی</string>
<string name="revanced_debug_screen_summary">فعال یا غیرفعال کردن گزینه‌های عیب یابی</string> <string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_screen_summary">فعال یا غیرفعال کردن گزینه‌های عیب یابی</string>
<string name="revanced_debug_title">گزارش عیب</string> <string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_title">گزارش عیب</string>
<string name="revanced_debug_summary_on">لاگ عیب فعال است</string> <string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_summary_on">لاگ عیب فعال است</string>
<string name="revanced_debug_summary_off">لاگ عیب غیرفعال است</string> <string name="shared.misc.debugging.enableDebuggingPatch.revanced_debug_summary_off">لاگ عیب غیرفعال است</string>
</patch> </patch>
<patch id="misc.privacy.sanitizeSharingLinksPatch"> <patch id="misc.privacy.sanitizeSharingLinksPatch">
</patch> </patch>
</app> </app>
<app id="youtube"> <app id="youtube">
<patch id="misc.settings.settingsPatch"> <patch id="misc.settings.settingsPatch">
<string name="revanced_settings_screen_00_about_title">درباره</string> <string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_00_about_title">درباره</string>
<string name="revanced_settings_screen_04_general_title">عمومی</string> <string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_04_general_title">عمومی</string>
<string name="revanced_settings_screen_05_player_title">اجراکننده</string> <string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_05_player_title">اجراکننده</string>
<string name="revanced_settings_screen_07_seekbar_title">نوار جریان پخش</string> <string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_07_seekbar_title">نوار جریان پخش</string>
<string name="revanced_settings_screen_12_video_title">ويدئو</string> <string name="youtube.misc.settings.settingsPatch.revanced_settings_screen_12_video_title">ويدئو</string>
</patch> </patch>
<patch id="misc.backgroundplayback.backgroundPlaybackPatch"> <patch id="misc.backgroundplayback.backgroundPlaybackPatch">
</patch> </patch>
@@ -108,8 +108,8 @@ Second \"item\" text"</string>
This item appear in the Subscriptions feed for future livestreams or unreleased videos. --> This item appear in the Subscriptions feed for future livestreams or unreleased videos. -->
<!-- 'Show more' should be translated with the same localized wording that YouTube displays. <!-- 'Show more' should be translated with the same localized wording that YouTube displays.
This button usually appears when searching for a YT creator. --> This button usually appears when searching for a YT creator. -->
<string name="revanced_hide_show_more_button_title">پنهان سازی دکمه \'نمایش بیشتر\'</string> <string name="youtube.layout.hide.general.hideLayoutComponentsPatch.revanced_hide_show_more_button_title">پنهان سازی دکمه \'نمایش بیشتر\'</string>
<string name="revanced_hide_ticket_shelf_title">پنهان سازی قفسه بلیط</string> <string name="youtube.layout.hide.general.hideLayoutComponentsPatch.revanced_hide_ticket_shelf_title">پنهان سازی قفسه بلیط</string>
<!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. --> <!-- 'People also watched' and 'You might also like' should be translated using the same localized wording YouTube displays. -->
<!-- https://logos.fandom.com/wiki/YouTube/Yoodles --> <!-- https://logos.fandom.com/wiki/YouTube/Yoodles -->
<!-- 'Join' should be translated using the same localized wording YouTube displays. <!-- 'Join' should be translated using the same localized wording YouTube displays.
@@ -241,12 +241,12 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="layout.sponsorblock.sponsorBlockResourcePatch"> <patch id="layout.sponsorblock.sponsorBlockResourcePatch">
<!-- Translations should use language similar to 'revanced_ryd_compact_layout_title'. --> <!-- Translations should use language similar to 'revanced_ryd_compact_layout_title'. -->
<string name="revanced_sb_general">عمومی</string> <string name="youtube.layout.sponsorblock.sponsorBlockResourcePatch.revanced_sb_general">عمومی</string>
<string name="revanced_sb_settings_copy">رونوشت‌</string> <string name="youtube.layout.sponsorblock.sponsorBlockResourcePatch.revanced_sb_settings_copy">رونوشت‌</string>
<!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. --> <!-- Toast shown if network connection times out. Translations of this should not be longer than the original English or the text can be clipped and not entirely shown. -->
<!-- A segment start and end time, such as "02:10 to 03:40". --> <!-- A segment start and end time, such as "02:10 to 03:40". -->
<!-- Shown in the settings preferences, and translations can be any text length. --> <!-- Shown in the settings preferences, and translations can be any text length. -->
<string name="revanced_sb_about_title">درباره</string> <string name="youtube.layout.sponsorblock.sponsorBlockResourcePatch.revanced_sb_about_title">درباره</string>
</patch> </patch>
<patch id="layout.formfactor.changeFormFactorPatch"> <patch id="layout.formfactor.changeFormFactorPatch">
</patch> </patch>
@@ -283,6 +283,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">
@@ -356,10 +358,10 @@ Second \"item\" text"</string>
<!-- Twitch specific internal debug mode, and not the same as 'revanced_debug_title'. --> <!-- Twitch specific internal debug mode, and not the same as 'revanced_debug_title'. -->
</patch> </patch>
<patch id="misc.settings.settingsPatch"> <patch id="misc.settings.settingsPatch">
<string name="revanced_about_title">درباره</string> <string name="twitch.misc.settings.settingsPatch.revanced_about_title">درباره</string>
<string name="revanced_twitch_debug_title">گزارش عیب</string> <string name="twitch.misc.settings.settingsPatch.revanced_twitch_debug_title">گزارش عیب</string>
<string name="revanced_twitch_debug_summary_on">لاگ عیب فعال است</string> <string name="twitch.misc.settings.settingsPatch.revanced_twitch_debug_summary_on">لاگ عیب فعال است</string>
<string name="revanced_twitch_debug_summary_off">لاگ عیب غیرفعال است</string> <string name="twitch.misc.settings.settingsPatch.revanced_twitch_debug_summary_off">لاگ عیب غیرفعال است</string>
</patch> </patch>
</app> </app>
</resources> </resources>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -38,8 +38,7 @@ Second \"item\" text"</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch> </patch>
<patch id="misc.fix.playback.spoofVideoStreamsPatch"> <patch id="misc.fix.playback.spoofVideoStreamsPatch">
<string name="revanced_spoof_video_streams_screen_summary">प्लेबैक समस्याओं को रोकने के लिए क्लाइंट वीडियो स्ट्रीम को स्पूफ करें</string> <string name="shared.misc.fix.playback.spoofVideoStreamsPatch.revanced_spoof_video_streams_screen_summary">प्लेबैक समस्याओं को रोकने के लिए क्लाइंट वीडियो स्ट्रीम को स्पूफ करें</string>
<string name="revanced_spoof_video_streams_screen_summary">प्लेबैक समस्याओं को रोकने के लिए क्लाइंट वीडियो स्ट्रीम को स्पूफ करें</string>
</patch> </patch>
<patch id="misc.audio.forceOriginalAudioPatch"> <patch id="misc.audio.forceOriginalAudioPatch">
<!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. --> <!-- 'Spoof video streams' should be the same translation used for 'revanced_spoof_video_streams_screen_title'. -->
@@ -231,6 +230,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -26,12 +26,12 @@ Second \"item\" text"</string>
<!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 --> <!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 -->
</patch> </patch>
<patch id="misc.checks.checkEnvironmentPatch"> <patch id="misc.checks.checkEnvironmentPatch">
<string name="revanced_check_environment_failed_title">Provjere nisu uspjele</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_title">Provjere nisu uspjele</string>
</patch> </patch>
<patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch"> <patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch">
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<string name="revanced_settings_save">Sačuvaj</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_save">Sačuvaj</string>
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code, <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. --> and changes made here must also be made there. -->
@@ -231,6 +231,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -26,25 +26,25 @@ Second \"item\" text"</string>
<!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 --> <!-- Translations of this should be identical to revanced_custom_branding_name_entry_5 -->
</patch> </patch>
<patch id="misc.checks.checkEnvironmentPatch"> <patch id="misc.checks.checkEnvironmentPatch">
<string name="revanced_check_environment_failed_title">ಪರಿಶೀಲನೆ ವಿಫಲವಾಗಿದೆ</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_title">ಪರಿಶೀಲನೆ ವಿಫಲವಾಗಿದೆ</string>
<string name="revanced_check_environment_dialog_open_official_source_button">ಅಧಿಕೃತ ಜಾಲತಾಣ ತೆರೆಯಿರಿ</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_dialog_open_official_source_button">ಅಧಿಕೃತ ಜಾಲತಾಣ ತೆರೆಯಿರಿ</string>
<string name="revanced_check_environment_dialog_ignore_button">ನಿರ್ಲಕ್ಷಿಸು</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_dialog_ignore_button">ನಿರ್ಲಕ್ಷಿಸು</string>
<string name="revanced_check_environment_failed_message">&lt;h5&gt;ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ನೀವು ಪ್ಯಾಚ್ ಮಾಡಿದಂತೆ ಕಾಣುತ್ತಿಲ್ಲ.&lt;/h5&gt;&lt;br&gt;ಈ ಅಪ್ಲಿಕೇಶನ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸದೇ ಇರಬಹುದು, &lt;b&gt; ಹಾಗು ಉಪಯೋಗಿಸಲು ಹಾನಿಕಾರಕ ಅಥವಾ ಅಪಾಯಕಾರಿಯಾಗಿರಬಹುದು&lt;/b&gt;.&lt;br&gt;&lt;br&gt;ಈ ಅಪ್ಲಿಕೇಶನ್ ಮೊದಲೇ ಪ್ಯಾಚ್ ಆಗಿದೆ ಅಥವಾ ಬೇರೆಯವರಿಂದ ಪಡೆದದ್ದು ಎಂದು ಈ ಪರಿಶೀಲನೆಗಳು ಸೂಚಿಸುತ್ತವೆ:&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;ನೀವು ದೃಢೀಕೃತ ಮತ್ತು ಸುರಕ್ಷಿತ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು &lt;b&gt;ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ ಮತ್ತು ನೀವೇ ಪ್ಯಾಚ್ ಮಾಡಿ&lt;/b&gt; ಎಂದು ಬಲವಾಗಿ ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ.&lt;p&gt;&lt;br&gt;ನಿರ್ಲಕ್ಷಿಸಿದರೆ, ಈ ಎಚ್ಚರಿಕೆಯನ್ನು ಎರಡು ಬಾರಿ ಮಾತ್ರ ತೋರಿಸಲಾಗುತ್ತದೆ.</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_failed_message">&lt;h5&gt;ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ನೀವು ಪ್ಯಾಚ್ ಮಾಡಿದಂತೆ ಕಾಣುತ್ತಿಲ್ಲ.&lt;/h5&gt;&lt;br&gt;ಈ ಅಪ್ಲಿಕೇಶನ್ ಸರಿಯಾಗಿ ಕಾರ್ಯನಿರ್ವಹಿಸದೇ ಇರಬಹುದು, &lt;b&gt; ಹಾಗು ಉಪಯೋಗಿಸಲು ಹಾನಿಕಾರಕ ಅಥವಾ ಅಪಾಯಕಾರಿಯಾಗಿರಬಹುದು&lt;/b&gt;.&lt;br&gt;&lt;br&gt;ಈ ಅಪ್ಲಿಕೇಶನ್ ಮೊದಲೇ ಪ್ಯಾಚ್ ಆಗಿದೆ ಅಥವಾ ಬೇರೆಯವರಿಂದ ಪಡೆದದ್ದು ಎಂದು ಈ ಪರಿಶೀಲನೆಗಳು ಸೂಚಿಸುತ್ತವೆ:&lt;br&gt;&lt;br&gt;&lt;small&gt;%1$s&lt;/small&gt;&lt;br&gt;ನೀವು ದೃಢೀಕೃತ ಮತ್ತು ಸುರಕ್ಷಿತ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಬಳಸುತ್ತಿರುವಿರಿ ಎಂದು ಖಚಿತಪಡಿಸಿಕೊಳ್ಳಲು &lt;b&gt;ಈ ಅಪ್ಲಿಕೇಶನ್ ಅನ್ನು ಅನ್‌ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ ಮತ್ತು ನೀವೇ ಪ್ಯಾಚ್ ಮಾಡಿ&lt;/b&gt; ಎಂದು ಬಲವಾಗಿ ಶಿಫಾರಸು ಮಾಡಲಾಗಿದೆ.&lt;p&gt;&lt;br&gt;ನಿರ್ಲಕ್ಷಿಸಿದರೆ, ಈ ಎಚ್ಚರಿಕೆಯನ್ನು ಎರಡು ಬಾರಿ ಮಾತ್ರ ತೋರಿಸಲಾಗುತ್ತದೆ.</string>
<string name="revanced_check_environment_not_same_patching_device">ಬೇರೆ ಸಾಧನದಲ್ಲಿ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_same_patching_device">ಬೇರೆ ಸಾಧನದಲ್ಲಿ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string>
<string name="revanced_check_environment_manager_not_expected_installer">ReVanced Manager ನಿಂದ ಸ್ಥಾಪಿಸಿದ್ದಲ್ಲ</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_manager_not_expected_installer">ReVanced Manager ನಿಂದ ಸ್ಥಾಪಿಸಿದ್ದಲ್ಲ</string>
<string name="revanced_check_environment_not_near_patch_time">10 ನಿಮಿಷಗಳಿಗಿಂತ ಮುಂಚೆ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time">10 ನಿಮಿಷಗಳಿಗಿಂತ ಮುಂಚೆ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string>
<string name="revanced_check_environment_not_near_patch_time_days">%s ದಿನಗಳ ಹಿಂದೆ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time_days">%s ದಿನಗಳ ಹಿಂದೆ ಪ್ಯಾಚ್ ಮಾಡಲಾಗಿದೆ</string>
<string name="revanced_check_environment_not_near_patch_time_invalid">APK ನಿರ್ಮಾಣ ದಿನಾಂಕವು ಭ್ರಷ್ಟಗೊಂಡಿದೆ</string> <string name="shared.misc.checks.checkEnvironmentPatch.revanced_check_environment_not_near_patch_time_invalid">APK ನಿರ್ಮಾಣ ದಿನಾಂಕವು ಭ್ರಷ್ಟಗೊಂಡಿದೆ</string>
</patch> </patch>
<patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch"> <patch id="misc.dns.checkWatchHistoryDomainNameResolutionPatch">
</patch> </patch>
<patch id="misc.settings.settingsResourcePatch"> <patch id="misc.settings.settingsResourcePatch">
<string name="revanced_settings_submenu_title">ಸಂಯೋಜನೆಗಳು</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_submenu_title">ಸಂಯೋಜನೆಗಳು</string>
<string name="revanced_settings_confirm_user_dialog_title">ನೀವು ಮುಂದುವರಿಯಲು ಖಚಿತವಾಗಿ ಬಯಸುತ್ತೀರಾ?</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_confirm_user_dialog_title">ನೀವು ಮುಂದುವರಿಯಲು ಖಚಿತವಾಗಿ ಬಯಸುತ್ತೀರಾ?</string>
<string name="revanced_settings_reset">ಮರುಹೊಂದಿಸಿ</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_reset">ಮರುಹೊಂದಿಸಿ</string>
<string name="revanced_settings_reset_color">ಬಣ್ಣ ಮರುಹೊಂದಿಸಿ</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_reset_color">ಬಣ್ಣ ಮರುಹೊಂದಿಸಿ</string>
<string name="revanced_settings_color_invalid">ಅಮಾನ್ಯ ಬಣ್ಣ</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_color_invalid">ಅಮಾನ್ಯ ಬಣ್ಣ</string>
<string name="revanced_settings_restart_title">ಮರುಪ್ರಾರಂಭದ ಅಗತ್ಯವಿದೆ</string> <string name="shared.misc.settings.settingsResourcePatch.revanced_settings_restart_title">ಮರುಪ್ರಾರಂಭದ ಅಗತ್ಯವಿದೆ</string>
<!-- Settings about dialog. --> <!-- Settings about dialog. -->
<!-- NOTE: the about strings above are duplicated in the TikTok about screen code, <!-- NOTE: the about strings above are duplicated in the TikTok about screen code,
and changes made here must also be made there. --> and changes made here must also be made there. -->
@@ -244,6 +244,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -224,12 +224,14 @@ Second \"item\" text"</string>
<!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. --> <!-- Translations of this should not be longer than the original English text, otherwise the text can be clipped and not entirely shown. -->
</patch> </patch>
<patch id="misc.announcements.announcementsPatch"> <patch id="misc.announcements.announcementsPatch">
<string name="revanced_announcements_dialog_dismiss">Melepaskan</string> <string name="youtube.misc.announcements.announcementsPatch.revanced_announcements_dialog_dismiss">Melepaskan</string>
</patch> </patch>
<patch id="misc.loopvideo.loopVideoPatch"> <patch id="misc.loopvideo.loopVideoPatch">
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<!-- <!--
All strings must have a unique path, even if the same string is declared in two different apps. All strings must have a unique path, even if the same string is declared in two different apps.
@@ -229,6 +229,8 @@ Second \"item\" text"</string>
</patch> </patch>
<patch id="misc.loopvideo.button.loopVideoButtonPatch"> <patch id="misc.loopvideo.button.loopVideoButtonPatch">
</patch> </patch>
<patch id="misc.audiofocus.pauseOnAudioInterruptPatch">
</patch>
<patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch"> <patch id="misc.dimensions.spoof.spoofDeviceDimensionsPatch">
</patch> </patch>
<patch id="misc.hapticfeedback.disableHapticFeedbackPatch"> <patch id="misc.hapticfeedback.disableHapticFeedbackPatch">

Some files were not shown because too many files have changed in this diff Show More